Java多线程详解

多线程


什么是进程?

正在执行的程序

什么是线程?

进程的子单位,一个能够完成独立功能的执行路径

为什么需要开启多线程

  • 当执行某些耗时操作的任务的时候需要开启多线程,防止线程阻塞
  • 能够让两个任务看起来像在同时执行
  • 提高CPU的使用率,进而提高进程和内存的使用率

为什么开启多线程会同时执行

因为CPU切换执行的速度太快了,肉眼无法察觉

开启多线程是不是越多越好,提高了效率还是降低了效率?

不是,线程越多,效率越慢,但是太少,浪费CPU资源,所以,合理利用CPU

并发和并行的区别

  • 并发:在同一时间段下同时执行多个线程,看起来像同时执行
  • 并行:在同一时间刻度下(不能够在分割的时间单位)执行多个线程,本质上就是同时执行

CPU在某个最小时间刻度单位下,执行的是一个进程的一个线程的一个不可分割原子性语句

举例:a++是线程安全的吗?不是

Java虚拟机的启动至少开启了两条线程,主线程和垃圾回收线程

一个线程可理解为进程的子任务

开启线程

线程的启动方式本质有两种:

  1. 继承Tread类的方式
  2. 实现Runnable的方式

方式一:继承Thread类

  1. 自定义类MyThread继承Thread
  2. MyThread类里面重写run()方法
  3. 创建线程对象
  4. 启动线程

注意:

  1. 启动线程使用的是start()方法而不是run()方法
  2. 线程不能多次启动

方式二:实现Runnable接口

  1. 自定义类MyRunnable实现Runnable接口
  2. 重写run()方法
  3. 创建MyRunnable类的对象
  4. 创建Thread类的对象,并把步骤3创建的对象作为构造参数传递启动线程

实现接口方式的好处

  1. 可以避免由于Java单继承带来的局限性
  2. 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,较好的体现了面向对象的设计思想

方式三:实现Callable方式开启线程

继承Thread和实现Runnable的方式的特点:

  1. 没有返回结果
  2. 没有异常

CallableRunnable的区别:

  1. Runnable无返回值,没有异常抛出
  2. Callable可以在启动线程中获取返回值,以及接受子线程的异常

线程间的数据传递:线程通信

A线程中开启了B线程

A——>B 通过构造方法

B——>A 通过Callable方式

public class ThreadDemo03 {
	public static void main(String[] args) {
		FutureTask<Integer> task = new FutureTask<>(new CalculateCallable(1,100));
		Thread t = new Thread(task);
		t.start();
		
		try {
			Integer i = task.get();
			System.out.println("计算结果: " + i);
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		System.out.println("over");
	}
}

class CalculateCallable implements Callable<Integer> {
	
	private int m;
	private int n;
	
	public CalculateCallable(int m, int n) {
		super();
		this.m = m;
		this.n = n;
	}
	
	public CalculateCallable() {
		super();
	}

	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int i = m; i <= n; i++) {
			System.out.println("子线程: " + i);
			sum += i;
			throw new NullPointerException("空空异常!!!");
		}
		return sum;
	}

	public int getM() {
		return m;
	}

	public void setM(int m) {
		this.m = m;
	}

	public int getN() {
		return n;
	}

	public void setN(int n) {
		this.n = n;
	}
	
} 

匿名内部类的方式开启线程

注意:当继承Thread和实现Runnable方式同时实现,继承Thread优先

public class ThreadDemo04 {
	public static void main(String[] args) {
		new Thread(); // 匿名线程对象
		
		// 方式一继承Thread方式开启线程
		new Thread() {
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println("A.继承Thread方式:" + i);
				}
			}
		}.start();
		
		// 方式二实现Runnable接口的方式开启线程
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println("B.实现Runnable接口的方式:" + i);
				}
			}
		}).start();
		
		
		for (int i = 0; i < 100; i++) {
			System.out.println("C.主线程:" + i);
		}
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println("D.实现Runnable接口的方式:" + i);
				}
			}
		}) {
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println("E.继承Thread方式:" + i);
				}
			}
		}.start();
	}
}

Lambda表达式开启线程

什么是Lambda表达式 :一种函数式接口的新的写法,本质还是匿名内部类,但是这个父类是函数式接口

什么是函数式接口

只有一个抽象方法的接口称为函数式接口

Runnable,FileFilter

Lambda表达式的语法:

​ ()—> {}

​ () 小括号里面是参数列表,如果一个函数式接口中的抽象方法没有参数,这里可以不写参数

​ —>固定格式

​ {}重写函数式接口的抽象方法的方法体

​ 如果方法体中只有一条语句,{}可以省略不写,如果返回值只有一条语句,return关键字可以省略

public class ThreadDemo05 {
	public static void main(String[] args) {
		new Demo().method(new ITest() {
			
			@Override
			public void show() {
				System.out.println("show");
			}
		});
		
		new Demo().method(()->System.out.println("Lambda表达式的 show"));
		
		new Demo().method((a, b)->System.out.println(a + "|" + b));
		
		new Demo().method((a, b)->a + b);
		
		// com.sxt.threaddemo
		File f = new File("src/com/sxt/threaddemo");
		/*File[] files = f.listFiles(new FileFilter() {
			
			@Override
			public boolean accept(File f) {
				return f.isFile() && f.getName().endsWith(".java");
			}
		});*/
		
		/*File[] files = f.listFiles((file) -> file.isFile() && file.getName().endsWith(".java"));
		
		for (File file : files) {
			System.out.println(file);
		}*/
		ArrayList<String> list = new ArrayList<>();
		list.add("张三丰");
		list.add("李四");
		list.add("赵六");
		list.add("王五哈哈哈");
		
		list.forEach((t)->{
			System.out.println(t);
		});
		
		list.forEach(new Consumer<String>() {

			@Override
			public void accept(String t) {
				System.out.println(t);
			}
		});
		
		list.removeIf((t)-> t.length() == 2);
		System.out.println(list);
		
		/*
		 * new Consumer<String>() {

			@Override
			public void accept(String t) {
				System.out.println(t);
			}
		}
		
		Consumer<? super E> action = new Consumer<String>() {

			@Override
			public void accept(String t) {
				System.out.println(t);
			}
		}
		
		public void forEach(Consumer<? super E> action) {
	        Objects.requireNonNull(action);
	        final int expectedModCount = modCount;
	        @SuppressWarnings("unchecked")
	        final E[] elementData = (E[]) this.elementData;
	        final int size = this.size;
	        for (int i=0; modCount == expectedModCount && i < size; i++) {
	            action.accept(elementData[i]);
	        }
	        if (modCount != expectedModCount) {
	            throw new ConcurrentModificationException();
	        }
	    }
		 */
		
		new Thread(()->{
//			System.out.println("Lambda表达式开启线程");
			for (int i = 0; i < 10000; i++) {
				System.out.println("Lambda:" + i);
			}
		}).start();
		
		for (int i = 0; i < 10000; i++) {
			System.out.println("main:" + i);
		}
	}
}

@FunctionalInterface
interface ITest {
	void show();
//	void test();
}

@FunctionalInterface
interface IDemo {
	void show(int a, String b);
//	void test();
}

@FunctionalInterface
interface IShow {
	int add(int a, int b);
//	void test();
}
class Demo {
	/*
	 * ITest test = new ITest() {
			
			@Override
			public void show() {
				System.out.println("show");
			}
		};
	 */
	public void method(ITest test) {
		test.show();	
	}
	
	public void method(IDemo d) {
		d.show(10, "sss");	
	}
	
	public void method(IShow s) {
		int add = s.add(100, 200);
		System.out.println(add);
	}
}

设置和获取线程名称的几种方式

  1. 通过构造方法
  2. 通过set/get方法
  3. 通过静态方法

主线程的名称叫做:main

public class ThreadDemo01 {
	public static void main(String[] args) {
		/*MyThread t = new MyThread();
		t.start();
		
		MyThread t2 = new MyThread();
		t2.start();*/
		
		/*MyThread t3 = new MyThread("隔壁老王");
		t3.start();*/
		/*Thread t3 = new Thread("隔壁老王");
		t3.start();*/
		
		/*MyThread t3 = new MyThread();
		t3.setName("隔壁老王家");
		t3.start();*/
		
		/*Thread.currentThread().setName("主线程");
		String name = Thread.currentThread().getName();
		System.out.println(name);*/
		
		Thread t4 = new Thread(new MyRunnable(), "隔壁老李");
		t4.start();
		
		/*long mainId = Thread.currentThread().getId();
		System.out.println(mainId);*/
	}
}

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(this.getName() + ":" + i);
		}
	}
}

class MyRunnable implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
		System.out.println("子线程: " + Thread.currentThread().getId());
	}
	
}
线程的常用方法

Java是如何对线程进行调度的?

java使用的是抢占式调度模型

抢占式调度模型

优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取CPU时间片相对多一些

设置和获取线程的优先级

public final int getPriority()
public final void setPriority(int newPriority)

线程休眠

public static void sleep(long millis)

可以用来制作时钟

public class ThreadDemo03 {
	public static void main(String[] args) {
		Thread t = new Thread(new MyClockRunnable(), "北京时间 ");
		t.start();
	}
}

class MyClockRunnable implements Runnable {

	@Override
	public void run() {
		while (true) {
			String s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
			System.out.println(Thread.currentThread().getName() + ":" + s);
			
			try {
				Thread.sleep(1000L);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
}

中断线程

public final void stop
public void interrupt

stopinterrupt的区别?

stop方法表示结束线程的生命

interrupt表示向线程抛出一个InterruptedException异常

//子线程睡眠10S中,主线程在子线程睡眠到第4S的时候,敲醒子线程
public class ThreadDemo04 {
	public static void main(String[] args) {
		
		Thread t = new Thread(new StopThread());
		t.start();
		System.out.println("主线程: 嘘,开始数,准备好");
		try {
			Thread.sleep(4000);
			// t.stop();
			t.interrupt();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

class StopThread implements Runnable {

	@Override
	public void run() {
		String startTime = new SimpleDateFormat("开睡时间: yyyy-MM-dd HH:mm:ss").format(new Date());
		System.out.println(startTime);
		
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			e.printStackTrace();
			System.out.println("谁打我!!!");
			System.out.println("打扰了,我自己来");
			System.exit(0);
		}
		
		String endTime = new SimpleDateFormat("觉醒时间: yyyy-MM-dd HH:mm:ss").format(new Date());
		System.out.println(endTime);
	}
	
}

后台线程

public final void setDaemon(boolean on)

一般来说,JVMJAVA虚拟机)中一般会包括两种线程

线程分类

  1. 用户线程
  2. 后台线程、守护线程、服务线程

所谓后台线程(daemon)线程指的是:在程序运行的时候在后台提供的一种通用的服务的线程,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程结束的时候,也就是用户线程都结束的时候,程序也就终止了。同时,会杀死进程中的所有的后台线程。反过来说,只要有任何非后台线程还在运行,程序就不会结束。比如执行main()的就是一个非后台线程。基于这个特点,当虚拟机中的用户线程全部退出运行时,守护线程没有服务的对象后,JVM也就退出了。

线程加入

public final void 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 = 0; i < 100; i++) {
			System.out.println(getName() + ":" + i);
		}
	}
}

线程礼让

public static void yield()

让正在执行的线程让出CPU的执行权一小会,t1t2互相争夺CPU的执行权,t1抢到了CPU的执行权,让出CPU的执行权,重新回到线程队列中继续抢夺CPU的执行权

public class ThreadDemo07 {
	public static void main(String[] args) {
		YieldThread t1 = new YieldThread();
		YieldThread t2 = new YieldThread();
		
		t1.setName("孔融");
		t2.setName("孔融的哥哥");
		
		t1.start();
		t2.start();
	}
}

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

线程同步

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

出现的问题
1.卖出了同票
窗口A正在出售第97张票
窗口B正在出售第97张票
窗口C正在出售第97张票

2.卖出了负票
窗口A正在出售第1张票
窗口C正在出售第0张票
窗口B正在出售第-1张票

问题产生的原因: CPU在某一个最小的时间刻度单位下,执行的是一个进程的一个线程的一个不可再分割的原子性语句

解决办法:
1.同步代码块
2.同步方法
3.同步锁

可能出现线程安全的问题的情况:
1.存在多线程环境
2.多个线程共享同一份数据
3.多个线程操作同一份数据并且共享数据做了修改
4.存在多条语句操作共享数据
在多线程环境下,存在多条语句操作共享数据,并且对数据做了修改的操作,那么必定会出现线程安全问题

解决办法: 将多条操作共享数据的语句 包裹起来,同步锁

public class ThreadSynchronizationDemo {
	public static void main(String[] args) {
		/*SellTicketThread t1 = new SellTicketThread();
		SellTicketThread t2 = new SellTicketThread();
		SellTicketThread t3 = new SellTicketThread();*/
		SellTicketThread st = new SellTicketThread();
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);
		Thread t3 = new Thread(st);
		
		t1.setName("窗口A");
		t2.setName("窗口B");
		t3.setName("窗口C");
		
		t1.start();
		t2.start();
		t3.start();
		
	}
}

/*
 * 解决办法方式一:同步代码块
	格式:
		synchronized(对象){需要同步的代码;}
	同步的好处
	解决了多线程的安全问题。
	同步的弊端
	当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,降低程序的运行效率。如果出现了同步嵌套,就容易产生死锁问题
	注意: 这里的对象是锁对象,可以是任意对象,但是必须多个线程共享同一个对象
		 这种方式加锁的时间是进入代码之后,释放锁的时间是代码块执行结束的时候

 */
/*class SellTicketThread implements Runnable {
	private int tickets = 100;
	@Override
	public void run() {
		while (true) {
			synchronized (MyLock.LOCK) {
				if (tickets > 0) {
					try {
						Thread.sleep(100L);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
				} 
			}
			
		}
	}
}*/

enum MyLock {
	LOCK
}

/*
 * 解决办法方式二:同步方法
	格式:
		public synchronized 返回值 方法名(参数列表) {
	      		//需要同步的代码块
	   	}
	
	如果锁对象是this,就可以考虑使用同步方法。
	
	如果方式静态方法, 当前类对应的字节码文件对象作锁 Class c = SellTicketThread.class
 */
/*class SellTicketThread implements Runnable {
	private static int tickets = 100;

	@Override
	public void run() {
		while (true) {
			sellTicket();
		}
	}
	
	public static synchronized void sellTicket() { 
		if (tickets > 0) {
			try {
				Thread.sleep(100L);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
		}
	}
}*/

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

class SellTicketThread implements Runnable {
	private static int tickets = 100;
	Lock lock = new ReentrantLock();
	@Override
	public void run() {
		while (true) {
			lock.lock();
			if (tickets > 0) {
				try {
					Thread.sleep(100L);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
			}
			lock.unlock();
		}
	}
}
线程死锁
  • 死锁:指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。
  • 代码演示死锁现象。
public class DeadThradDemo {
public static void main(String[] args) {
	DieLock dl1 = new DieLock(true);
	DieLock dl2 = new DieLock(false);
dl1.start();
dl2.start();
}
}
class DieLock extends Thread {
	private boolean flag;
public DieLock() {
}

public DieLock(boolean flag) {
	this.flag = flag;
}

@Override
public void run() {
	if (flag) {
		// dl1进来
		synchronized (TestLock.LOCKA) {
			System.out.println("if 语句中 LockA锁"); // 就在输出完这句话之后被dl2抢到了资源
			synchronized (TestLock.LOCKB) {
				System.out.println("if 语句中 LockB锁");
			}
		}
	} else {
		// dl走else
		synchronized (TestLock.LOCKB) {
			System.out.println("else 语句中 lockB锁");
			synchronized (TestLock.LOCKA) {
				System.out.println("else 语句中 lockA锁");
			}
		}
	}
}
}

class TestLock {
	public static final Object LOCKA = new Object();
	public static final Object LOCKB = new Object();
}
线程池和线程组

线程池和线程组的区别?

线程组:

线程组存在的意义,首要原因是安全。

java默认创建的线程都是属于系统线程组,而同一个线程组的线程是可以相互修改对方的数据的。

但如果在不同的线程组中,那么就不能“跨线程组”修改数据,可以从一定程度上保证数据安全.

线程池:
线程池存在的意义,首要作用是效率。
线程的创建和结束都需要耗费一定的系统时间(特别是创建),不停创建和删除线程会浪费大量的时间。所以,在创建出一条线程并使其在执行完任务后不结束,而是使其进入休眠状态,在需要用时再唤醒,那么 就可以节省一定的时间。
如果这样的线程比较多,那么就可以使用线程池来进行管理。保证效率。

线程组和线程池共有的特点:

1,都是管理一定数量的线程

2,都可以对线程进行控制—包括休眠,唤醒,结束,创建,中断(暂停)–但并不一定包含全部这些操作。

//线程组
public class ThreadGroupDemo {
	public static void main(String[] args) {
		ThreadGroup tg = Thread.currentThread().getThreadGroup();
		System.out.println(tg.getName());
		
		ThreadGroupRunnable tr = new ThreadGroupRunnable();
		
		ThreadGroup sgyy = new ThreadGroup("三国演义组");
		// 第一组 三国演义组
		Thread t1 = new Thread(sgyy, tr, "诸葛亮");
		Thread t2 = new Thread(sgyy, tr, "司马懿");
		Thread t3 = new Thread(sgyy, tr, "周瑜");
		
		ThreadGroup shz = new ThreadGroup("水浒传");
		// 第一组 三国演义组
		Thread t4 = new Thread(shz, tr, "李逵");
		Thread t5 = new Thread(shz, tr, "宋江");
		Thread t6 = new Thread(shz, tr, "卢俊义");
		
		/*t1.start();
		t2.start();
		t3.start();*/
		List<Thread> threadList = new ArrayList<Thread>();
		threadList.add(t1);
		threadList.add(t2);
		threadList.add(t3);
		
		// 批量设置为后台线程
		sgyy.setDaemon(true);
		
		sgyy.stop();
		
		for (Thread thread : threadList) {
			thread.start();
		}
		
		System.out.println(t5.getThreadGroup().getName());
	}
}

class ThreadGroupRunnable implements Runnable {

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

Executors工厂类来产生线程池。

//构造方法
	public static ExecutorService newCachedThreadPool()
	public static ExecutorService newFixedThreadPool(int nThreads)
	public static ExecutorService newSingleThreadExecutor()

示例:

/*使用线程池 创建三个线程
		1.打印 A-Z
		2.计算m~n的和
		3.拷贝文件*/
public class ThreadPoolDemo {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ExecutorService pool = Executors.newFixedThreadPool(3);
		pool.submit(new MyRunnable());
		Future<Integer> future = pool.submit(new MyCallable(1, 100));
		pool.submit(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println(Thread.currentThread().getName() + ":" + i);
				}
			}
		});
		Integer i = future.get();
		System.out.println("返回的结果: " + i);
		
		pool.shutdown();
	}
}

class MyRunnable implements Runnable {

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

class MyCallable implements Callable<Integer> {
	
	int m;
	int n;

	public MyCallable(int m, int n) {
		super();
		this.m = m;
		this.n = n;
	}

	public MyCallable() {
		super();
	}

	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int i = m; i <= n; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
			sum += i;
		}
		return sum;
	}
	
}
生产者与消费者模型

对于此模型,应该明确一下几点:

1、生产者仅仅在仓储未满时候生产,仓满则停止生产。

2、消费者仅仅在仓储有产品时候才能消费,仓空则等待。

3、当消费者发现仓储没产品可消费时候会通知生产者生产。

4、生产者在生产出可消费产品时候,应该通知等待的消费者去消费。

此模型将要结合java.lang.ObjectwaitnotifynotifyAll方法来实现以上的需求。这是非常重要的。

下面通过代码来演示:

商品

//商品
public class Toy {
	private String toyName;
	private int num;
	// 表示是否有玩具, true表示有,false表示没有
	private boolean flag;
	public Toy() {
		super();
	}
	public Toy(int num, String toyName, boolean flag) {
		super();
		this.num = num;
		this.toyName = toyName;
		this.flag = flag;
	}
	public String getToyName() {
		return toyName;
	}
	public void setToyName(String toyName) {
		this.toyName = toyName;
	}
	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 [toyName=" + toyName + ", num=" + num + ", flag=" + flag + "]";
	}
	
	public synchronized void product(Toy t) {
		// 先判断是否有玩具
		if (this.isFlag()) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		// 没有,就生产玩具
		this.setToyName(t.getToyName());
		this.setNum(t.getNum());
		
		// 修改标志位,表示有玩具
		this.setFlag(t.isFlag());
		// 生产完毕之后,就通知消费者消费
		this.notify();
	}
	
	public synchronized void consume() {
		// 先判断是否有玩具
		if (!this.isFlag()) {
			// wait方法
			try {
				// 没有玩具,就等待!
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		// 有玩具就消费
		String toyName = this.getToyName();
		int num = this.getNum();
		System.out.println(toyName + "|" + num);
		this.setNum(--num);
		
		// 消费完毕之后,就通知生产者生产
		if (num <= 0) {
			this.setFlag(false);
			// 通知生产者生产
			this.notify();
		}
	}
}

消费者

// 消费者模型
public class GetThread extends Thread {
	
	// 共享资源
	private Toy t;
	
	public GetThread() {
		super();
	}
	
	public GetThread(Toy t) {
		super();
		this.t = t;
	}

	@Override
	public void run() {
		while (true) {
			t.consume();
		}
	}

	public Toy getT() {
		return t;
	}

	public void setT(Toy t) {
		this.t = t;
	}
	
}

生产者

public class SetThread extends Thread {
	private Toy t;
	// 控制如果是偶数,就生产铁胆火车侠,如果是奇数,就生产金刚狼
	public int i;

	public SetThread() {
		super();
	}

	public SetThread(Toy t) {
		super();
		this.t = t;
	}
	
	@Override
	public void run() {
		while (true) {
			synchronized (t) {
				if (i % 2 == 0) {
					t.product(new Toy(10, "火影忍者", true));
				} else {
					t.product(new Toy(5, "变形金刚", true));
				}
				i++;
				
			}
		}
		
	}

	public Toy getT() {
		return t;
	}

	public void setT(Toy t) {
		this.t = t;
	}
	
	
}
volatile关键字

所谓原子性,就是某系列的操作步骤要么全部执行,要么都不执行。

比如,变量的自增操作 i++,分三个步骤:

1.从内存中读取出变量 i 的值

2.将 i 的值加1

3.将 加1 后的值写回内存
假设i的值为10,A线程执行到第二步, i变成11, B线程抢到了执行权,B读取内存中的数据还是10,执行第二步i=11

最后结果是11,预期结果是12,线程不安全

这说明 i++ 并不是一个原子操作。因为,它分成了三步,

有可能当某个线程执行到了第2步时被中断了,那么就意味着只执行了其中的两个步骤,没有全部执行。
以下程序期望的结果应该是: 100*100=10000,但是,实际上count并没有达到10000

volatile修饰的变量并不保证对它的操作(自增)具有原子性。

AtomicInteger

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

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

1:int a = 1;

2:int b = 2;

3:boolean flag = true;

4.if(flag)

指令重排序后:

–>

3:boolean flag = true;

2:int b = 2;

1:int a = 1;

如何保证线程安全? volatile + synchronized

synchronized和volatile的区别

1.volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法

2.volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。

3.synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区

从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。

用法示例

/*现在有两个线程,一个是main线程,另一个是RunThread。
	它们都试图修改 第三行的 isRunning变量。
	按照JVM内存模型,main线程将isRunning读取到本地线程内存空间,修改后,再刷新回主内存。
	线程会一直在私有堆栈中读取isRunning变量。
	因此,RunThread线程无法读到main线程改变的isRunning变量
	从而出现了死循环,导致RunThread无法终止。
	解决方法,在第三行代码处用 volatile 关键字修饰即可。
	这里,它强制线程从主内存中取 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 volatile 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("线程执行完成了");
    }
}
ThreadLocal

线程安全问题: 多线程环境下,存在多条原子性语句操作共享数据,并且对数据做了写的操作,会出现线程安全问题,而ThreadLocal为变量在每个线程中都创建了一个副本,那样每个线程可以访问自己内部的副本变量,这样就解决了安全问题。

代码示例:

public class ThreadLocalDemo {
	public static void main(String[] args) {
		User user = new User("隔壁老王", "123456");
		// MyThreadLocal<User> tl = new MyThreadLocal<User>();
		ThreadLocal<User> tl = new ThreadLocal<>();
		tl.set(user);
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				tl.set(new User("隔壁老李", "789456"));
				System.out.println(tl.get());
			}
		}).start();
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println(tl.get());
	}
}

// 容器,这个容器用来绑定 线程 和 数据 Map
class MyThreadLocal<T> {
	
	private HashMap<Thread, T> map;
	
	public MyThreadLocal() {
		map = new HashMap<>();
	}

	public void put(T t) {
		//		main = new User("隔壁老王", "123456")
		map.put(Thread.currentThread(), t);
	}
	
	public T get() {
		return map.get(Thread.currentThread());
	}
}

class User {
	private String userName;
	private String password;
	public User() {
		super();
	}
	public User(String userName, String password) {
		super();
		this.userName = userName;
		this.password = password;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@Override
	public String toString() {
		return "User [userName=" + userName + ", password=" + password + "]";
	}
	
}

定时器

Timer定时器对象和TimerTask 定时器任务

代码示例:

public class TimerTest {
	public static void main(String[] args) {
		Timer t = new Timer();
		TimerTask task = new MyTask(t);
		Calendar c = Calendar.getInstance();
		c.set(2019, 7, 24, 15, 24, 30);
		long time = c.getTimeInMillis();
		Date d = new Date(time);
		// 规定制定的时间启动线程
		// t.schedule(task, d);
		
		// t.schedule(task, 3000L);
		
		t.schedule(task, 3000, 1000);
	}
}

class MyTask extends TimerTask {
	
	private Timer t;
	
	public MyTask(Timer t) {
		super();
		this.t = t;
	}

	public MyTask() {
		super();
	}

	@Override
	public void run() {
		System.out.println("Boom!!!");
		// t.cancel();
	}
	
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值