多线程(二)

如何设置和获取线程的名称

通过构造方法

Thread(String name)分配新的 Thread 对象。
Thread(Runnable target, String name)分配新的 Thread 对象。

通过线程的成员方法

public final String getName()
public final void setName(String name)

通过静态方法

public static Thread currentThread()可以获取任意方法所在的线程名称
可以获取主线程的线程名称:Thread.currentThread().getName()

long getId() 返回该线程的标识符

public class ThreadDemo01 {
	public static void main(String[] args) {
//		MyThread t1 = new MyThread("张三");
//		MyThread t2 = new MyThread("李四");
//		MyThread t1 = new MyThread();
//		MyThread t2 = new MyThread();
//		
//		t1.setName("老王");
//		t2.setName("老李");
		
		MyRunnable mr = new MyRunnable();
		Thread t1 = new Thread(mr, "老孙");
		Thread t2 = new Thread(mr, "老邓");
		Thread t3 = new Thread(mr, "老王");
		
		System.out.println(t1.getId());
		System.out.println(t2.getId());
		System.out.println(t3.getId());
		t1.start();
		t2.start();
		
//		Thread currentThread = Thread.currentThread();
//		System.out.println(currentThread.getName());
	}
}

class MyRunnable implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
	
}

class MyThread extends Thread {
	
	public MyThread() {
		super();
	}
	
	public MyThread(String name) {
		super(name);
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
}

抢占式调度模型

Java是如何对线程进行调度的?
Java使用的是抢占式调度模型

​ 抢占式调度模型
​ 优先让优先级高的线程使用 CPU,如果线程的优先级相同,
​ 那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
​ 设置和获取线程的优先级
public final int getPriority()
public final void setPriority(int newPriority)

public class ThreadDemo02 {
	public static void main(String[] args) {
		PriorityThread pt = new PriorityThread();
		Thread t1 = new Thread(pt, "刘备");
		Thread t2 = new Thread(pt, "关羽");
		Thread t3 = new Thread(pt, "张飞");
		
//		System.out.println(t1.getPriority());
//		System.out.println(t2.getPriority());
//		System.out.println(t3.getPriority());
		
//		java.lang.IllegalArgumentException
		t1.setPriority(Thread.MAX_PRIORITY);
		t2.setPriority(Thread.NORM_PRIORITY);
		t3.setPriority(Thread.MIN_PRIORITY);
		
		t1.start();
		t2.start();
		t3.start();
		
	}
}

class PriorityThread implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
	
}

多线程的几个常见方法

线程休眠

public static void sleep(long millis)

public class ThreadDemo03 {
	public static void main(String[] args) {
		Thread t = new Thread(new SleepThread(), "时钟线程");
		t.start();
	}
}

// 利用线程休眠模拟时钟
class SleepThread implements Runnable {

	@Override
	public void run() {
		while (true) {
			String dateStr = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date());
			System.out.println(Thread.currentThread().getName() + "的当前时间为: " + dateStr);
			
			// 休眠1秒
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
}

中断线程

public final void stop() 已过时方法

public void interrupt()

比较两个方法的不同之处

​ stop:结束线程的生命
​ interrupt: 会给线程抛出一个中断异常,对应的线程可以处理或者结束

public class ThreadDemo04 {
	public static void main(String[] args) {
		Thread t = new Thread(new StopThread(), "马云");
		t.start();
		
		try {
			Thread.sleep(3000);
			t.interrupt();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

class StopThread implements Runnable {

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + "睡眠起始时间:" + new Date());
		
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			System.out.println("有人打断我了");
//			e.printStackTrace();
			System.exit(0);
		}
		
		System.out.println(Thread.currentThread().getName() + "睡眠起床时间:" + new Date());
	}
	
}

后台线程

public final void setDaemon(boolean on)

一般来说,JVM(JAVA虚拟机)中一般会包括俩种线程,分别是用户线程后台线程

所谓后台线程(daemon)线程指的是:在程序运行的时候在后台提供的一种通用的服务的线程,并且这种线程并不属于程序中不可或缺的部分。

因此,当所有的非后台线程结束的时候,也就是用户线程都结束的时候,程序也就终止了。同时,会杀死进程中的所有的后台线程。反过来说,只要有任何非后台线程还在运行,程序就不会结束。

1比如执行main()的就是一个非后台线程。

基于这个特点,当虚拟机中的用户线程全部退出运行时,守护线程没有服务的对象后,JVM也就退出了。

public class ThreadDemo05 {
	public static void main(String[] args) {
		DaemonThread t1 = new DaemonThread();
		DaemonThread t2 = new DaemonThread();
		DaemonThread t3 = new DaemonThread();
		
		t1.setName("武器大师");
		t2.setName("小法师");
		t3.setName("光辉女郎");
		
        //其他线程都被设置为后台线程
		t1.setDaemon(true);
		t2.setDaemon(true);
		t3.setDaemon(true);
		
		t1.start();
		t2.start();
		t3.start();
		
        //主线程设置为大水晶,水晶被摧毁后,整个进程结束
		Thread.currentThread().setName("大水晶");
		for (int i = 1; i <= 10; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
		
	}
}

class DaemonThread extends Thread {
	
	@Override
	public void run() {
		for (int i = 1; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
}

线程加入

public final void join()

线程加入后会优先执行被加入的线程,并且会将此线程执行完

join方法要再对应的线程启动后就马上调用

public class ThreadDemo06 {
	public static void main(String[] args) {
		JoinThread t1 = new JoinThread();
		JoinThread t2 = new JoinThread();
		JoinThread t3 = new JoinThread();
		
		t1.setName("老张");
		t2.setName("老李");
		t3.setName("老王");
		
		t1.start();
		try {
			t1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.start();
		try {
			t2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t3.start();
		
	}
}

class JoinThread extends Thread {
	@Override
	public void run() {
		for (int i = 1; i <= 100; i++) {
			System.out.println(getName() + ":" + i);
		}
	}
}

线程礼让

public static void yield()

在对应的时间刻度上让出CPU的控制权,同时马上进入就绪状态继续和其他线程抢夺控制权

public class ThreadDemo07 {
	public static void main(String[] args) {
		YieldThread t1 = new YieldThread();
		YieldThread t2 = new YieldThread();
		YieldThread t3 = new YieldThread();
		
		t1.setName("华妃");
		t2.setName("杨贵妃");
		t3.setName("武则天");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

class YieldThread extends Thread {
	
	@Override
	public void run() {
		for (int i = 1; i <= 100; i++) {
			System.out.println(getName() + ":" + i);
			Thread.yield();
		}
	}
}

线程同步

情景模拟:

需求:深圳罗湖火车站目前正在出售车票,共有100张票,而它有3个售票窗口售票,
请设计一个程序模拟该火车站售票。

分析:

三个窗口共享同一个份代码,同一份资源,但是都是独立执行

使用继承Thread类开启多线程执行,需要new出三个对象,就会造成100张票的数据不唯一,设置成static修饰的静态方法后依然会出现卖出同一张票的状况

这里需要说明的是CPU的执行最小单元是程序的原子性语句,所以不管是使用上面的哪种方式开启多线程都有卖出同一张票或多卖出票的隐患

涉及到线程安全问题,产生线程安全的因素:

  1. 必须存在多线程环境
  2. 至少有两条语句操作了共享数据
  3. 如果多个线程中有一个线程对共享数据进行了写操作

综合来说:在多线程环境下,至少有两条以上的原子性语句操作了共享数据,并且这个操作是写操作,肯定会出现线程安全问题

引入线程同步解决此问题

同步代码块

​ 格式:synchronized(锁对象){需要同步的代码;}

注意:

		1. 锁对象是任意对象
		2. 不同线程共享同一把锁
  			3. 同步方法的锁对象是 this
     			4. 如果方法是静态方法,锁对象是字节码文件对象 

同步的好处:解决了多线程的安全问题。

同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,降低程序的运行效率。如果出现了同步嵌套,就容易产生死锁问题

同步方法

格式:public synchronized 返回值 方法名(参数列表) {//需要同步的代码块}

Lock锁

以下演示代码:

public class ThreadDemo01 {
	public static void main(String[] args) {
		SellTicketThread stt = new SellTicketThread();
		
		Thread t1 = new Thread(stt);
		Thread t2 = new Thread(stt);
		Thread t3 = new Thread(stt);
		
//		SellTicketThread t1 = new SellTicketThread();
//		SellTicketThread t2 = new SellTicketThread();
//		SellTicketThread t3 = new SellTicketThread();
		
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

// 继承Thread类
class SellTicketThread extends Thread {
	private static int tickets = 100;
	
	@Override
	public void run() {
		while (true) {
			if (tickets > 0) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
			}
			System.out.println(getName() + "正在出售第" + (tickets --) + "张票");
				
			} 
			
			this.sellTickets();
		}
	}
	
	public synchronized void sellTickets() {
		if (tickets > 0) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets --) + "张票");
			
		}
	}
}

class SellTicketThread implements Runnable {

	private int tickets = 100;
	Lock lock = new ReentrantLock();

	@Override
	public void run() {
		while (true) {
			if (tickets > 0) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets --) + "张票");
				
			}
            
			//synchronized修饰的同步代码块
			synchronized (MyLock.LOCK) {
				if (tickets > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets --) + "张票");
					
				}
			}
			
			sellTickets();
			
            //方法三,Lock锁,注意后面需要解锁
			lock.lock();
			if (tickets > 0) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets --) + "张票");
				
			}
			lock.unlock();
		}
	}
    //方法二,synchronized修饰的同步方法
	public synchronized void sellTickets() {
		if (tickets > 0) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets --) + "张票");
			
		}
	}
	
}
//自定义锁对象
class MyLock {
	public static final Object LOCK = new Object();
}

死锁

死锁指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。

死锁的发生必须具备以下四个必要条件:

  1. **互斥条件:**指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
  2. **请求和保持条件:**指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
  3. **不剥夺条件:**指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
  4. **环路等待条件:**指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

一个死锁案例:

public class DieThreadDemo {
	public static void main(String[] args) {
		DieLock t1 = new DieLock(true);
		DieLock t2 = new DieLock(false);
		
		t1.start();
		t2.start();
	}	
}

class DieLock extends Thread{
	private boolean flag;
	
	@Override
	public void run() {	
		if (flag) {
			synchronized (MyLock.LOCKA) {
				System.out.println("if语句中的LOCKA锁");
                //同步代码块中嵌套同步,容易出现死锁
				synchronized (MyLock.LOCKB) {
					System.out.println("if语句中的LOCKB锁");
				}
			}
		}else {
			synchronized (MyLock.LOCKB) {
				System.out.println("else语句中的LOCKB锁");
				synchronized (MyLock.LOCKA) {
					System.out.println("else语句中的LOCKA锁");
				}
			}
		}
	}
	public DieLock(boolean flag) {
		super();
		this.flag = flag;
	}
	public DieLock() {
		super();
	}
	public boolean isFlag() {
		return flag;
	}
	public void setFlag(boolean flag) {
		this.flag = flag;
	}	
}
class MyLock {
	public static final Object LOCKA = new Object(); 
	public static final Object LOCKB = new Object(); 
}

线程池

当程序中要创建大量生存期很短的线程时,应该考虑使用线程池,程序启动一个新线程占用资源大,使用线程池可以很好的提高性能,线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

Executors 工厂类来产生线程池。

构造方法:(都是静态方法)

public static ExecutorService newCachedThreadPool()

public static ExecutorService newFixedThreadPool(int nThreads)

public static ExecutorService newSingleThreadExecutor()

public class ThreadPool {
	public static void main(String[] args) {
		//创建线程池
		ExecutorService pool = Executors.newFixedThreadPool(3);
		//方式一,通过new Callable实现类的方式开启线程,同时Callable接口的Run方法可以接收返回值
		Future<Integer> future = pool.submit(new MyCallable());
		try {
			System.out.println("1-100的和为:"+future.get());
		} catch (InterruptedException | ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//方式二,通过new Runnable的方式开启线程
		pool.submit(new MyRunnable());
		//方式三,跟方式二一样,使用匿名内部类实现
		pool.submit(new Runnable() {			
			@Override
			public void run() {
				for (int i = 1; i <= 100; i++) {
					System.out.println(i);
				}
			}
		});	
		//需要关闭线程池
		pool.shutdown();	
	}
}

class MyCallable implements Callable<Integer>{
	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int i = 1; i <= 100; i++) {
			System.out.println(i);
			sum += i;
		}
		return sum;
	}	
}

class MyRunnable implements Runnable{
	@Override
	public void run() {
		for (int i = 1; i <= 100; i++) {
			System.out.println(i);
		}
	}
}

线程组

线程组方便对一批线程进行管理 本质可以理解为 Thread threads[]

public class ThreadDemo02 {
	public static void main(String[] args) {
		
		// 创建小组
		ThreadGroup tg1 = new ThreadGroup("上课坐飞机");
		ThreadGroup tg2 = new ThreadGroup("学霸");
		
		MyRunnable mr = new MyRunnable();
		
		Thread t1 = new Thread(tg1, mr, "赵磊");
		Thread t2 = new Thread(tg1, mr, "刘彦明");
		Thread t3 = new Thread(tg1, mr, "刘召");
		
		Thread t4 = new Thread(tg2, mr, "徐邦贵");
		Thread t5 = new Thread(tg2, mr, "王子龙");
		Thread t6 = new Thread(tg2, mr, "黄鹏");
		
//		Thread[] threads = new Thread[3];
//		int enumerate = tg1.enumerate(threads);
//		System.out.println(Arrays.toString(threads));
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
		t6.start();
		
		System.out.println(tg1.activeCount());
		tg1.setDaemon(true);
		tg1.stop();
		System.out.println(tg1.getName());
		
		// 获取主线程所在的组?
		String mainThreadGroupName = Thread.currentThread().getThreadGroup().getName();
		System.out.println(mainThreadGroupName);
		
	}
}

class MyGroupRunnable implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
	
}

线程通信

线程和线程之间实现数据交互

通过构造方法 A -> B A在启动之前传递给B

通过实现Callable接口的方式 A -> B B在执行结束之后将数据回传给A

利用接口回调方式传递数据

利用同步锁串行实现数据传递

利用等待唤醒机制

接口回调

  1. 创建一个回调接口[电话号码],接口中书写方法 A->B

    ​ 方法的形参就是 B线程使用形参传递给A线程

    ​ 方法的返回值就是 A线程使用返回值传递给B线程

  2. A线程将这个回调接口通过构造方法传递给B线程

  3. B线程在使用这个接口,即接口回调

    代码示例

    public class ThreadDemo01 {
    	public static void main(String[] args) {
    		AThread a = new AThread();
    		a.setName("A");
    		a.start();
    		
    	}
    }
    
    class AThread extends Thread {
    	@Override
    	public void run() {
    		
    		BThread b = new BThread();
    		b.setName("B");
    		b.setCb(new ICallBack() {
    			
    			@Override
    			public int fun(int a, int b, String message) {
    				int sum = a + b;
    				System.out.println("收到了来自于B线程的消息: " + message);
    				return sum;
    			}
    		});
    		b.start();
    		
    		for (int i = 0; i < 100; i++) {
    			System.out.println(Thread.currentThread().getName() + "线程正在执行!");
    		}
    		
    	}
    }
    
    class BThread extends Thread {
    	
    	private ICallBack cb;
    	
    	public BThread() {}
    	
    	public BThread(String name, ICallBack cb) {
    		super(name);
    		this.cb = cb;
    	}
    	
    	public void setCb(ICallBack cb) {
    		this.cb = cb;
    	}
    	
    	@Override
    	public void run() {
    		for (int i = 0; i < 100; i++) {
    			System.out.println(Thread.currentThread().getName() + "线程正在执行!");
    		}
    		// 这里你想要传递什么数据就通过实参,实参你可以自己任意的确定 B->A
    		int result = cb.fun(10, 20, "我是线程B带过来两个整数数据");
    		// 这里的result是 A线程【调用者】传递给B线程 A->B
    		System.out.println("收到了来自于A线程的数据: " + result);
    	}
    	
    }
    
    // 回调接口
    interface ICallBack {
    	/**
    	 * 回调接口
    	 * @param a
    	 * 		是B线程 传递 A线程的数据
    	 * @param b
    	 * 		是B线程 传递 A线程的数据
    	 * @param message
    	 * 		是B线程 传递 A线程的数据
    	 * @return
    	 * 		是A线程 传递 B线程的数据
    	 */
    	int fun(int a, int b, String message);
    }
    

    同步锁串行

    这种方式,本质上就是"共享资源"式的通信。

    多个线程需要访问同一个共享变量,哪个线程获取到了锁对象就可以执行。

    public class ThreadDemo02 {
    	public static void main(String[] args) {
    		MyObject object = new MyObject();
    
    		// 线程A与线程B 持有的是同一个对象:object
    		ThreadA a = new ThreadA(object);
    		ThreadB b = new ThreadB(object);
    		ThreadC c = new ThreadC(object);
    		
    		a.start();
    		b.start();
    		c.start();
    		
    		try {
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		
    		System.out.println(object.getData());
    	}
    }
    
    // 锁对象
    class MyObject {
    	
    	private String data = "";
    
    	public synchronized void methodA(String message) {
    		System.out.println(message);
    		data += message;
    	}
    
    	public synchronized void methodB(String message) {
    		
    		System.out.println(message);
    		data += message;
    	}
    
    	public synchronized void methodC(String message) {
    		System.out.println(message);
    		data += message;
    	}
    	
    	public void setData(String data) {
    		this.data = data;
    	}
    	
    	public String getData() {
    		return data;
    	}
    }
    
    class ThreadA extends Thread {
    
    	private MyObject object;
    
    	public ThreadA() {
    		super();
    	}
    
    	public ThreadA(MyObject object) {
    		super();
    		this.object = object;
    	}
    
    	@Override
    	public void run() {
    		object.methodA("A");
    	}
    }
    
    class ThreadB extends Thread {
    
    	private MyObject object;
    
    	public ThreadB() {
    		super();
    	}
    
    	public ThreadB(MyObject object) {
    		super();
    		this.object = object;
    	}
    
    	@Override
    	public void run() {
    		object.methodB("B");
    	}
    }
    
    class ThreadC extends Thread {
    
    	private MyObject object;
    
    	public ThreadC() {
    	}
    
    	public ThreadC(MyObject object) {
    		this.object = object;
    	}
    
    	@Override
    	public void run() {
    		object.methodC("C");
    	}
    	
    }
    

    等待唤醒机制

    等待唤醒机制依赖的几个方法

    wait:让当前线程处理等待(阻塞)状态,会将当前线程放入到等待池中,并且会释放锁对象

    void wait(long timeout)

    void wait(long timeout, int nanos)

    notify:唤醒在同一把锁上的处于等待的单个线程

    nofityAll:唤醒在同一把锁上处于等待的所有线程

    此处示例用到生产消费者模型,如下:

    /*生产者消费者模型
     * 
     * 生产者
     * 		先判断是否有玩具
     * 			有,就等待消费者消费  wait
     * 			没有,就生产
     * 				生产完毕之后通知消费者消费  nofity
     * 
     * 消费者
     * 		先判断是否有玩具
     * 			有,就消费
     * 				消费完毕之后,就通知生产者生产
     * 			没有,就等待生产者生产
     * 
     * 该模型是否存在线程安全问题 --> 存在
     */
    
    //测试类
    public class ThreadDemo01 {
    	public static void main(String[] args) {
    		Toy t = new Toy();
    		
    		SetThread st = new SetThread(t);
    		GetThread gt = new GetThread(t);
    		
    		st.start();
    		gt.start();
    	}
    }
    

    生产者:

    //生产者模型
    public class SetThread extends Thread {
    	private Toy t;
    	private int i;
    
    	public SetThread() {
    		super();
    	}
    
    	public SetThread(Toy t) {
    		super();
    		this.t = t;
    	}
    	
    	@Override
    	public void run() {
    		while (true) {
    			synchronized (t) {
    				// 先判断是否有玩具
    				if (t.isFlag()) {
    					// 就等待消费者消费
    					try {
    						t.wait();
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    				}
    				
    				// 没有,就生产
    				if (i % 2 == 0) {
    					t.setName("Hello Kitty");
    					t.setNum(10);
    				} else {
    					t.setName("King");
    					t.setNum(5);
    				}
    				i++;				
    				// 设置标志位,表示目前已经有数据了
    				t.setFlag(true);
    				
    				// 生产完毕之后通知消费者消费  nofity
    				t.notify();
    			}
    		}		
    	}
    	public Toy getT() {
    		return t;
    	}
    
    	public void setT(Toy t) {
    		this.t = t;
    	}	
    }
    

    消费者:

    public class GetThread extends Thread {
    	private Toy t;
    	public GetThread() {
    	}
    	public GetThread(Toy t) {
    		this.setT(t);
    	}	
    	@Override
    	public void run() {		
    		while (true) {
    			synchronized (t) {
    				// 先判断是否有玩具
    				if (!t.isFlag()) {
    					// 没有,就等待生产者生产
    					try {
    						t.wait();
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    				}				
    				// 有,就消费
    				String toyName = t.getName();
    				int toyNum = t.getNum();
    				System.out.println(toyName + "|" + toyNum);
    				// 消费一次数量减一
    				t.setNum(--toyNum);
    				
    				// 消费完毕之后,就通知生产者生产
    				if (toyNum <= 0) {
    					// 设置标志位,说明没有玩具了
    					t.setFlag(false);
    					// 消费完毕之后,就通知生产者生产
    					t.notify();
    				}
    			}
    		}
    		
    	}
    	public Toy getT() {
    		return t;
    	}
    	public void setT(Toy t) {
    		this.t = t;
    	}		
    }
    

    Toy类,也可用作锁对象,实现数据共享

    public class Toy {
    	private String name; // 玩具名称
    	private int num; // 玩具数量
    	private boolean flag; // 是否有玩具, 有表示true,没有表示false
    
    	public Toy() {
    		super();
    	}
    
    	public Toy(String name, int num, boolean flag) {
    		super();
    		this.name = name;
    		this.num = num;
    		this.flag = flag;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public int getNum() {
    		return num;
    	}
    	public void setNum(int num) {
    		this.num = num;
    	}
    	public boolean isFlag() {
    		return flag;
    	}
    	public void setFlag(boolean flag) {
    		this.flag = flag;
    	}
    	@Override
    	public String toString() {
    		return "Toy [name=" + name + ", num=" + num + ", flag=" + flag + "]";
    	}
    }
    

    volatile关键字

    示例引入:

    public class ThreadDemo {
        public static void main(String[] args) {
            try {
                RunThread thread = new RunThread();
                thread.start();
                Thread.sleep(1000);
                thread.setRunning(false);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    class RunThread extends Thread {
    
        private boolean isRunning = true;
    
        public boolean isRunning() {
            return isRunning;
        }
    
        public void setRunning(boolean isRunning) {
            this.isRunning = isRunning;
        }
    
        @Override
        public void run() {
            System.out.println("进入到run方法中了");
            while (isRunning == true) {
            }
            System.out.println("线程执行完成了");
        }
    }
    

    以上代码运行结果,根据我们自己的判断应该是

    先显示进入到run方法中了,后停顿1秒,然后显示线程执行完成了

    实际结果:程序阻塞
    在这里插入图片描述

    出现该问题的原因是因为JVM在执行程序时,读取的内存是副本,一个线程更改后,另一个线程的副本并未修改

    使用volatile关键字修饰变量isRunning即可

    private volatile boolean isRunning = true

    volatitle关键字的作用

    1. 保证变量在线程间可见,对volatile变量所有的写操作都能立即反应到其他线程中,换句话说,volatile变量在各个线程中是一致的

    2. 禁止指令的重排序优化; 指令重排序 , 这里间接可以保证线程安全

    volatitlesynchronized的区别

    1. volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
    2. volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。
    3. synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。

    本地线程

    ThreadLocal

    针对每一个线程都提供对应的副本数据

    这样操作每次只操作自己副本数据,保证了线程的安全,同时也提高了效率,但是操作的不是同一份数据

    public class ThreadLocalDemo {
    	public static void main(String[] args) {
    //		MyLocalThread<User> tl = new MyLocalThread<User>();
    		ThreadLocal<User> tl = new ThreadLocal<User>();
    		tl.set(new User("隔壁老王", 18));
    		
    		new Thread(new Runnable() {
    			
    			@Override
    			public void run() {
    				User user = new User("隔壁老李", 25);
    				tl.set(user);
    				
    				user.setName("hello kitty");
    				System.out.println(tl.get());
    			}
    		}).start();	
    		try {
    			Thread.sleep(1000L);
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}		
    		System.out.println(tl.get());
    	}
    }
    
    class MyLocalThread<T> {
    	private HashMap<Thread, T> hm;
    
    	public MyLocalThread() {
    		hm = new HashMap<>();
    	}
    	
    	// 添加数据到map中
    	public void set(T t) {
    		hm.put(Thread.currentThread(), t);
    	}
    	
    	// 通过线程对象获取数据
    	public T get() {
    		return hm.get(Thread.currentThread());
    	}	
    }
    
    class User {
    	private String name;
    	private int age;
    	
    	public User() {
    		super();
    	}	
    	public User(String name, int age) {
    		super();
    		this.name = name;
    		this.age = age;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public int getAge() {
    		return age;
    	}
    	public void setAge(int age) {
    		this.age = age;
    	}
    	@Override
    	public String toString() {
    		return "User [name=" + name + ", age=" + age + "]";
    	}
    	
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值