多线程
什么是进程?
正在执行的程序
什么是线程?
进程的子单位,一个能够完成独立功能的执行路径
为什么需要开启多线程
- 当执行某些耗时操作的任务的时候需要开启多线程,防止线程阻塞
- 能够让两个任务看起来像在同时执行
- 提高
CPU
的使用率,进而提高进程和内存的使用率
为什么开启多线程会同时执行
因为CPU
切换执行的速度太快了,肉眼无法察觉
开启多线程是不是越多越好,提高了效率还是降低了效率?
不是,线程越多,效率越慢,但是太少,浪费CPU
资源,所以,合理利用CPU
并发和并行的区别
- 并发:在同一时间段下同时执行多个线程,看起来像同时执行
- 并行:在同一时间刻度下(不能够在分割的时间单位)执行多个线程,本质上就是同时执行
CPU
在某个最小时间刻度单位下,执行的是一个进程的一个线程的一个不可分割原子性语句
举例:a++
是线程安全的吗?不是
Java
虚拟机的启动至少开启了两条线程,主线程和垃圾回收线程
一个线程可理解为进程的子任务
开启线程
线程的启动方式本质有两种:
- 继承
Tread
类的方式 - 实现
Runnable
的方式
方式一:继承Thread类
- 自定义类
MyThread
继承Thread
类 MyThread
类里面重写run()
方法- 创建线程对象
- 启动线程
注意:
- 启动线程使用的是
start()
方法而不是run()
方法 - 线程不能多次启动
方式二:实现Runnable接口
- 自定义类
MyRunnable
实现Runnable
接口 - 重写
run()
方法 - 创建
MyRunnable
类的对象 - 创建
Thread
类的对象,并把步骤3创建的对象作为构造参数传递启动线程
实现接口方式的好处
- 可以避免由于
Java
单继承带来的局限性 - 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,较好的体现了面向对象的设计思想
方式三:实现Callable方式开启线程
继承Thread
和实现Runnable
的方式的特点:
- 没有返回结果
- 没有异常
Callable
和Runnable
的区别:
Runnable
无返回值,没有异常抛出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);
}
}
设置和获取线程名称的几种方式
- 通过构造方法
- 通过
set/get
方法 - 通过静态方法
主线程的名称叫做: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
stop
和interrupt
的区别?
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)
一般来说,JVM
(JAVA
虚拟机)中一般会包括两种线程
线程分类
- 用户线程
- 后台线程、守护线程、服务线程
所谓后台线程(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
的执行权一小会,t1
和t2
互相争夺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.Object
的wait
与notify
、notifyAll
方法来实现以上的需求。这是非常重要的。
下面通过代码来演示:
商品
//商品
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();
}
}