Java线程学习笔记(一)

前言

此文为学习葛一鸣、郭超的《实战Java高并发程序设计》的学习笔记

线程是什么?与进程有什么关系?

在电脑中,一个可以运行的文件,都是程序,但是他们都是“死的”,静态。运行之后,他就会变成一个进程,此时就“活了”。在任务管理器中,一个个动态运行的就是进程。
在这里插入图片描述
进程中包含多个线程,进程就像一个机器,线程就是机器的各个零件,他们要一起分工合作才能保证这个进程顺利运行。

Java实现

在Java语言中,新建一个线程,需要new一个Thread对象
运行一个线程则是通过start()

Thread t = new Thread();
t.start();

线程在start()之后会运行run()方法,但是直接使用run()仅仅是作为一个普通的方法调用,却不能新建一个线程。如下代码

Thread t = new Thread();
t.run();
创建一个线程
  • 可以通过继承Thread,重载run()方法实现
public class extendThread extends Thread{
	
	public void run() {
		System.out.println("Hello");
	}
}
  • 实现Runnable接口
public class extendThread implements Runnable {
	@Override
	public void run() {
		System.out.println("Hello");
	}
}
public class Main {

	public static void main(String[] args) {

		Thread t = new Thread(new extendThread());
		t.start();
		
	}

}

继承Thread和实现Runnable接口都是常用的方法,但是实现建议Runnable接口,因为可以避免单继承的限制,比较灵活。

终止线程 stop()

终止线程不建议使用stop(),因为stop()是强制将一个线程终止,不管这个线程此时是否将程序执行完了,如果是写操作,这可能会导致写到一半,剩下一半没有写完的情况。如下

id = 001, name = 张三, sex = 男

如果需要修改为

id = 001, name = 李四, sex = 女

写入了name值之后,被stop()方法强制结束了,可能导致这样的后果

id = 001 , name = 李四, sex = 男

sex值没有修改,这样子的错误是致命的

线程中断

在Thread中有三个关于线程中断的方法

public void interrupt()                                //中断线程
public boolean isInterrupted()                   //判断是否被中断
public static boolean interrupted()            //判断是否被中断,并清除中断状态

Thread.interrupt():将一个进程设置中断标志位,中断标志位表示当前线程已经被中断了

Thread.isInterrupted():判断当前线程是否有被中断(通过检查中断标志位)

Thread.interrupted():判断当前线程的中断状态,并清除当前线程的中断标志位状态

public class Main {

	public static void main(String[] args) throws InterruptedException {

		Thread t = new Thread(new extendThread());
		t.start();
		Thread.sleep(2000);
		
		t.interrupt();
		
	}

}

还是以上面实现Runnable接口的extendThread类为例,运行上面的程序依然打印出结果
在这里插入图片描述
上面的程序虽然有Thread.interrupt()中断方法,中断没有发生任何作用,因为Thread.interrupt()只是设置了中断标志位,而在实例对象中没有处理中断的逻辑代码

public class extendThread implements Runnable {
	
	int index = 1;

	@Override
	public void run() {
		
		while(true) {
			
			if(Thread.currentThread().isInterrupted()) {   //判断
				System.out.println("Interrupted");
				break;
			}
			
			System.out.println("Hello" + index++);
		}
		
	}

}
public class Main {

	public static void main(String[] args) throws InterruptedException {

		Thread t = new Thread(new extendThread());
		t.start();

		Thread.sleep(200);
		
		t.interrupt();
		
	}

}

运行结果:

…(省略)
Hello14406
Hello14407
Hello14408
Hello14409
Hello14410
Interrupted

Thread.sleep()方法会让当前的线程休眠。它会抛出一个异常,并且需要去处理这个异常

等待(wait)和通知(notify)

wait()和notify()是某一个对象上使用的两个方法

线程A在一个对象上使用了obj.wait(),当前线程就是在这个对象的使用上进行等待,我的理解是交出了对象的控制权

直到某个线程X使用了obj.notify()为止

但是如果有多个线程使用了obj.wait(),即是有对个线程等待对象的控制权,notify()会在等待队列中 随机 选一个唤醒

wait()方法和notify()方法需要在synchronized(object){};语句中使用

public class SimleWN {
	final static Object object = new Object();
	public static class T1 extends Thread{
		
		public void run() {
			
			synchronized (object) {
				
				System.out.println(System.currentTimeMillis() + ":T1 start! ");
				
				try {
					System.out.println(System.currentTimeMillis() + ":T1 wait for object ");
					//交出object控制权
					object.wait();
				}catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(System.currentTimeMillis() + ":T1 end! ");
				
			}
			
		}
		
	}
	public static class T2 extends Thread{
		
		public void run() {
			
			synchronized (object) {
				
				System.out.println(System.currentTimeMillis() + ":T2 start! ");
				
				//唤醒其中一个
				object.notify();
				System.out.println(System.currentTimeMillis() + ":T2 end! ");
				
				try {
					Thread.sleep(2000);
				}catch (InterruptedException e) {
					e.printStackTrace();
				}
				
			}
			
		}
		
	}

	public static void main(String[] args) {
		Thread t1 = new T1();
		Thread t2 = new T2();
		
		t1.start();
		t2.start();
	}

}

结果:

1554044597679:T1 start!
1554044597680:T1 wait for object
1554044597680:T2 start!
1554044597680:T2 end!
1554044599681:T1 end!

上面的代码执行过程:当 t1 开始执行run()方法打印

1554044597679:T1 start!
1554044597680:T1 wait for object

之后,开始交出对象控制权,在此之前 t2 线程一直处于等待状态,当 t1 得到对象控制权之后,马上开始打印

1554044597680:T2 start!

之后开始唤醒 t1 线程,但是 t1 线程却不能立刻开始运行,因为此时的对象控制权还没有交出来所以 t1 还是处于等待的状态

当 t2 打印

1554044597680:T2 end!

并睡眠2秒后(可以看到时间戳),结束程序,即交出的对象控制权

此时 t1 获取对象控制权,开始打印最后一句

1554044599681:T1 end!

挂起(suspend)和继续执行(resume)

这一对方法与wait()、notify()很像

suspend()是将线程挂起,必须等待resume()才可以继续执行

区别在于,suspend()不会释放任何锁资源,它仅仅只是暂停运行了而已,这样就容易出现一个问题,如果resume()在suspend()之前执行,会导致被永远挂起,锁也是不会释放

public class BadSuspend {
    public static Object u = new Object();
    static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    static ChangeObjectThread t2 = new ChangeObjectThread("t2");

    public static class ChangeObjectThread extends Thread{
        public ChangeObjectThread(String name) {
            super.setName(name);
        }


        public void run() {
            synchronized (u){
                System.out.println("in "+ getName());
                Thread.currentThread().suspend();
            }
        }

    }

    // 导致resume不生效的执行顺序可能是这样的:
    // 打印t1 => t1在suspend => t2等待u释放 => t1被resume => t2被resume => u释放打印t2 => t2被suspend => 永远无法结束
    public static void main(String []args) throws InterruptedException {
        t1.start();
        Thread.sleep(100);
        t2.start();
        t1.resume();
        t2.resume();
        t1.join();
        t2.join();
    }
}

因为是在synchronized语句中执行,所以当 t1 开始线程之后就获得了对象锁

因为主线程睡眠了0.1秒,当 t2 开始线程之后,t1 已经挂起了,所以 t2 是一直是就绪状态等待对象锁

因为主线程和 t1 线程在运行上几乎是同时,也可以想象成 t1.resume()、t2.resume()是同时执行

此时 t1 线程也是同时执行,也就是说,当主线程执行t2.resume()时,t1线程还没有执行完,没有释放对象锁

所以 t2 线程是在主线程执行了t2.resume()之后才被挂起的,而此时就被永远的挂起了

等待线程结束(join)和谦让(yield)

如果在A线程中调用了B.join()方法,A线程就会等待B线程结束之后才结束

还可以在join()方法加入参数,如join(1000),意味着等待一秒之后如果还没有结束就不等

public class JoinMain {
    public static volatile int i = 0;

    public static class AddThread extends Thread{


        public void run() {
            for(i=0;i<10000000;i++){
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        AddThread at = new AddThread();
        at.start();
        at.join();
        System.out.println(i);
    }
}

输出结果总是10000000

如果不是调用了join(),at线程可能没有执行完,主线程就将 i 打印出来了

join()的核心代码

while(isAlive){
	wait(0);
}

当线程完成之后,被等待的线程会在退出前调用notifyAll()通知所有等待线程继续执行

所以要尽量的避免在Thread对象实例上使用wait()或者notify()方法,因为这很有可能会影响系统API的工作,或者被系统API所影响

yield()方法会是当前线程让出CPU,之后处于就绪的状态,也就是说很有可能这个线程会继续抢到CPU资源

volatile与Java内存模型(JMM)

Java内存模型说的是原子性有序性可见性

volatile的存在就是用来确保线程间的可见性

//可见性
public class NoVisibility {

    private static boolean ready;
    private static int number;

    private static class ReaderThread extends Thread{


        public void run() {
            while(!ready);
            System.out.println(number);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new ReaderThread().start();
        Thread.sleep(1000);
        number = 42;
        ready = true;
        Thread.sleep(1000);
    }
}

上述代码中,主线程修改ready的值,ReaderThread线程是无法看到的,所以也就是永远无法打印出number,但是如果加上volatile,就可以解决这个问题

volatile关键字是如何保证可见性的

线程组

线程组可以将功能相同的线程分组

public class ThreadGroupName implements Runnable {

    public void run() {
        String groupAndName = Thread.currentThread().getThreadGroup().getName() + Thread.currentThread().getName();
        while (true){
            System.out.println(groupAndName);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args){
    	//创建一个叫PrintGroup的线程组
        ThreadGroup tg = new ThreadGroup("PrintGroup");
        
        //将t1、t2线程放进这个线程组
        Thread t1 = new Thread(tg, new ThreadGroupName(), "T1");
        Thread t2 = new Thread(tg, new ThreadGroupName(), "T2");
        t1.start();
        t2.start();
        System.out.println(tg.activeCount());
        tg.list();
    }
}
守护线程(Daemon)

守护线程会随着所有非守护线程结束而结束

如果设置了 t 为主线程的守护线程,那么主线程结束之后,t 线程就会自动的结束

比如垃圾回收线程,JIT线程就是守护线程

public class DeamonDemo {

    public static class DeamonT extends Thread{
        public void run() {
            while (true){
                System.out.println("I am alive");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new DeamonT();
        t.setDaemon(true);
        t.start();
        Thread.sleep(2000);
    }
}

这里需要注意的是,必须要在线程start之前设置为守护线程,不然会报错

线程优先级

线程可以设置1到10的优先级,高优先级在竞争资源时会有优势,但这里仅仅的概率问题,低优先级也有可能竞争成功

Thread.setPriority()就可以调用

synchronized

synchronized关键字是将需要同步的代码加锁,使得每次只能有一个进程进入同步快,从而保证线程间的安全性

synchronized有三种常用的方法

  • 指定加锁的对象:对给定对象加锁,进入同步代码前要获得给定对想的锁。
  • 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁
  • 直接作用于静态方法:相当于当前类加锁,进入同步代码前要获得当前类的锁

给对象加锁

public class AccountingSync implements Runnable{
	static AccountingSync instance = new AccountingSync();
	static int i = 0;
	
	@Override
	public void run() {
		for(int j = 0; j < 10000000; j++) {
			synchronized (instance) {
				i++;
			}
		}
	}


	public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
	}
}

输出结果:

20000000

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值