多线程的概述(并发编程)
/**
目标:多线程的概述。(并发编程)
什么是进程?
程序是静止的,运行中的程序就是进程。
进程的三个特征:
1.动态性 : 进程是运行中的程序,要动态的占用内存,CPU和网络等资源。
2.独立性 : 进程与进程之间是相互独立的,彼此有自己的独立内存区域。
3.并发性 : 假如CPU是单核,同一个时刻其实内存中只有一个进程在被执行。
CPU会分时轮询切换依次为每个进程服务,因为切换的速度非常
快,给我们的感觉这些进程在同时执行,这就是并发性。
并行:同一个时刻同时有多个在执行。
什么是线程?
线程是属于进程的。一个进程可以包含多个线程,这就是多线程。
线程是进程中的一个独立执行单元。
线程创建开销相对于进程来说比较小。
线程也支持“并发性”。
线程的作用:
可以提高程序的效率,线程也支持并发性,可以有更多机会得到CPU。
多线程可以解决很多业务模型。
大型高并发技术的核心技术。
设计到多线程的开发可能都比较难理解。
*/
public class ThreadDemo {
}
线程的注意事项
/**
目标:线程的注意事项。
1.线程的启动必须调用start()方法。否则当成普通类处理。
-- 如果线程直接调用run()方法,相当于变成了普通类的执行,此时将只有主线程在执行他们!
-- start()方法底层其实是给CPU注册当前线程,并且触发run()方法执行
2.建议线程先创建子线程,主线程的任务放在之后。否则主线程永远是先执行完!
*/
public class ThreadDemo {
// 启动这个类,这个类就是进程,它自带一个主线程,
// 是main方法,main就是一个主线程的执行!!
public static void main(String[] args) {
// 3.创建一个新的线程对象。
Thread t = new MyThread();
// 4. 调用线程对象的start()方法启动线程。最终还是执行run()方法!
// 如果线程直接调用run()方法,相当于变成了普通类的执行,此时将只有主线程在执行他们!此时其实还是只有一个线程就是主线程执行
// t.run();
// start()方法底层其实是给CPU注册当前线程,并且触发run()方法执行
t.start();
for(int i = 0 ; i < 10 ; i++ ) {
System.out.println("main线程输出:"+i);
}
}
}
// 1.定义一个线程类继承Thread。线程类并不是线程对象,用来创建线程对象的。
class MyThread extends Thread{
// 2.重写run()方法
@Override
public void run() {
for(int i = 0 ; i < 10 ; i++ ) {
System.out.println("子线程输出:"+i);
}
}
}
线程的创建方式一
/**
目标:线程的创建方式一。
多线程是很有用的,我们在进程中创建线程的方式有三种:
(1)直接定义一个类继承线程类Thread,重写run()方法,创建线程对象
调用线程对象的start()方法启动线程。
(2)定义一个线程任务类实现Runnable接口,重写run()方法,创建线程任务对象,把
线程任务对象包装成线程对象, 调用线程对象的start()方法启动线程。
(3)实现Callable接口(拓展)。
a.继承Thread类的方式
-- 1.定义一个线程类继承Thread类。
-- 2.重写run()方法
-- 3.创建一个新的线程对象。
-- 4.调用线程对象的start()方法启动线程。
继承Thread类的优缺点:
优点:编码简单。
缺点:线程类已经继承了Thread类无法继承其他类了,功能不能通过继承拓展(单继承的局限性)
小结:
线程类是继承了Thread的类。
启动线程必须调用start()方法。
多线程是并发抢占CPU执行,所以在执行的过程中会出现并发随机性。
*/
public class ThreadDemo {
// 启动后的ThreadDemo当成一个进程。
// main方法是由主线程执行的,理解成main方法就是一个主线程
public static void main(String[] args) {
// 3.创建一个线程对象
Thread t = new MyThread();
// 4.调用线程对象的start()方法启动线程,最终还是执行run()方法!
t.start();
for(int i = 0 ; i < 100 ; i++ ){
System.out.println("main线程输出:"+i);
}
}
}
// 1.定义一个线程类继承Thread类。
class MyThread extends Thread{
// 2.重写run()方法
@Override
public void run() {
// 线程的执行方法。
for(int i = 0 ; i < 100 ; i++ ){
System.out.println("子线程输出:"+i);
}
}
}
/**
目标:线程的创建方式一。
多线程是很有用的,我们在进程中创建线程的方式有三种:
(1)直接定义一个类继承线程类Thread,重写run()方法,创建线程对象
调用线程对象的start()方法启动线程。
(2)定义一个线程任务类实现Runnable接口,重写run()方法,创建线程任务对象,把
线程任务对象包装成线程对象, 调用线程对象的start()方法启动线程。
(3)实现Callable接口(拓展)。
a.继承Thread类的方式
-- 1.定义一个线程类继承Thread类。
-- 2.重写run()方法
-- 3.创建一个新的线程对象。
-- 4.调用线程对象的start()方法启动线程。
继承Thread类的优缺点:
优点:编码简单。
缺点:线程类已经继承了Thread类无法继承其他类了,功能不能通过继承拓展(单继承的局限性)
小结:
线程类是继承了Thread的类。
启动线程必须调用start()方法。
多线程是并发抢占CPU执行,所以在执行的过程中会出现并发随机性。
*/
public class ThreadDemo {
// 启动后的ThreadDemo当成一个进程。
// main方法是由主线程执行的,理解成main方法就是一个主线程
public static void main(String[] args) {
// 3.创建一个线程对象
Thread t = new MyThread();
// 4.调用线程对象的start()方法启动线程,最终还是执行run()方法!
t.start();
for(int i = 0 ; i < 100 ; i++ ){
System.out.println("main线程输出:"+i);
}
}
}
// 1.定义一个线程类继承Thread类。
class MyThread extends Thread{
// 2.重写run()方法
@Override
public void run() {
// 线程的执行方法。
for(int i = 0 ; i < 100 ; i++ ){
System.out.println("子线程输出:"+i);
}
}
}
线程的创建方式二
/**
目标:线程的创建方式二。
多线程是很有用的,我们在进程中创建线程的方式有三种:
(1)直接定义一个类继承线程类Thread,重写run()方法,创建线程对象
调用线程对象的start()方法启动线程。
(2)定义一个线程任务类实现Runnable接口,重写run()方法,创建线程任务对象,把
线程任务对象包装成线程对象, 调用线程对象的start()方法启动线程。
(3)实现Callable接口(拓展)。
b.实现Runnable接口的方式。
-- 1.创建一个线程任务类实现Runnable接口。
-- 2.重写run()方法
-- 3.创建一个线程任务对象。
-- 4.把线程任务对象包装成线程对象
-- 5.调用线程对象的start()方法启动线程。
Thread的构造器:
-- public Thread(){}
-- public Thread(String name){}
-- public Thread(Runnable target){}
-- public Thread(Runnable target,String name){}
实现Runnable接口创建线程的优缺点:
缺点:代码复杂一点。
优点:
-- 线程任务类只是实现了Runnable接口,可以继续继承其他类,而且可以继续实现其他接口(避免了单继承的局限性)
-- 同一个线程任务对象可以被包装成多个线程对象
-- 适合多个多个线程去共享同一个资源(后面内容)
-- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立。
-- 线程池可以放入实现Runable或Callable线程任务对象。(后面了解)
注意:其实Thread类本身也是实现了Runnable接口的。
-- 不能直接得到线程执行的结果!
*/
public class ThreadDemo {
public static void main(String[] args) {
// 3.创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程的任务的)
Runnable target = new MyRunnable();
// 4.把线程任务对象包装成线程对象.且可以指定线程名称
// Thread t = new Thread(target);
Thread t = new Thread(target,"1号线程");
// 5.调用线程对象的start()方法启动线程
t.start();
Thread t2 = new Thread(target);
// 调用线程对象的start()方法启动线程
t2.start();
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}
// 1.创建一个线程任务类实现Runnable接口。
class MyRunnable implements Runnable{
// 2.重写run()方法
@Override
public void run() {
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}
/**
目标:线程的创建方式二。
多线程是很有用的,我们在进程中创建线程的方式有三种:
(1)直接定义一个类继承线程类Thread,重写run()方法,创建线程对象
调用线程对象的start()方法启动线程。
(2)定义一个线程任务类实现Runnable接口,重写run()方法,创建线程任务对象,把
线程任务对象包装成线程对象, 调用线程对象的start()方法启动线程。
(3)实现Callable接口(拓展)。
b.实现Runnable接口的方式。
-- 1.创建一个线程任务类实现Runnable接口。
-- 2.重写run()方法
-- 3.创建一个线程任务对象。
-- 4.把线程任务对象包装成线程对象
-- 5.调用线程对象的start()方法启动线程。
Thread的构造器:
-- public Thread(){}
-- public Thread(String name){}
-- public Thread(Runnable target){}
-- public Thread(Runnable target,String name){}
实现Runnable接口创建线程的优缺点:
缺点:代码复杂一点。
优点:
-- 线程任务类只是实现了Runnable接口,可以继续继承其他类,而且可以继续实现其他接口(避免了单继承的局限性)
-- 同一个线程任务对象可以被包装成多个线程对象
-- 适合多个多个线程去共享同一个资源(后面内容)
-- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立。
-- 线程池可以放入实现Runable或Callable线程任务对象。(后面了解)
注意:其实Thread类本身也是实现了Runnable接口的。
-- 不能直接得到线程执行的结果!
*/
public class ThreadDemo {
public static void main(String[] args) {
// 3.创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程的任务的)
Runnable target = new MyRunnable();
// 4.把线程任务对象包装成线程对象.且可以指定线程名称
// Thread t = new Thread(target);
Thread t = new Thread(target,"1号线程");
// 5.调用线程对象的start()方法启动线程
t.start();
Thread t2 = new Thread(target);
// 调用线程对象的start()方法启动线程
t2.start();
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}
// 1.创建一个线程任务类实现Runnable接口。
class MyRunnable implements Runnable{
// 2.重写run()方法
@Override
public void run() {
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}
方式二的匿名内部类写法。简化写法
/**
目标:方式二的匿名内部类写法。简化写法。
*/
public class ThreadDemo02 {
public static void main(String[] args) {
// 创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程的任务的)
Runnable target = new Runnable() {
@Override
public void run() {
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
};
// 把线程任务对象包装成线程对象.且可以指定线程名称
Thread t = new Thread(target);
// 调用线程对象的start()方法启动线程
t.start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}).start();
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}
线程的创建方式三
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
拓展:线程的创建方式三。(拓展)
多线程是很有用的,我们在进程中创建线程的方式有三种:
(1)直接定义一个类继承线程类Thread,重写run()方法,创建线程对象
调用线程对象的start()方法启动线程。
(2)定义一个线程任务类实现Runnable接口,重写run()方法,创建线程任务对象,把
线程任务对象包装成线程对象, 调用线程对象的start()方法启动线程。
(3)实现Callable接口(拓展)。
c.线程的创建方式三: 实现Callable接口。
-- 1,定义一个线程任务类实现Callable接口 , 申明线程执行的结果类型。
-- 2,重写线程任务类的call方法,这个方法可以直接返回执行的结果。
-- 3,创建一个Callable的线程任务对象。
-- 4,把Callable的线程任务对象包装成一个未来任务对象。
-- 5.把未来任务对象包装成线程对象。
-- 6.调用线程的start()方法启动线程
优缺点:
优点:全是优点。
-- 线程任务类只是实现了Callable接口,可以继续继承其他类,而且可以继续实现其他接口(避免了单继承的局限性)
-- 同一个线程任务对象可以被包装成多个线程对象
-- 适合多个多个线程去共享同一个资源(后面内容)
-- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立。
-- 线程池可以放入实现Runable或Callable线程任务对象。(后面了解)
-- 能直接得到线程执行的结果!
缺点:编码复杂。
*/
public class ThreadDemo {
public static void main(String[] args) {
// 3.创建一个Callable的线程任务对象
Callable call = new MyCallable();
// 4.把Callable任务对象包装成一个未来任务对象
// -- public FutureTask(Callable<V> callable)
// 未来任务对象是啥,有啥用?
// -- 未来任务对象其实就是一个Runnable对象:这样就可以被包装成线程对象!
// -- 未来任务对象可以在线程执行完毕之后去得到线程执行的结果。
FutureTask<String> task = new FutureTask<>(call);
// 5.把未来任务对象包装成线程对象
Thread t = new Thread(task);
// 6.启动线程对象
t.start();
for(int i = 1 ; i <= 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+" => " + i);
}
// 在最后去获取线程执行的结果,如果线程没有结果,让出CPU等线程执行完再来取结果
try {
String rs = task.get(); // 获取call方法返回的结果(正常/异常结果)
System.out.println(rs);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 1.创建一个线程任务类实现Callable接口,申明线程返回的结果类型
class MyCallable implements Callable<String>{
// 2.重写线程任务类的call方法!
@Override
public String call() throws Exception {
// 需求:计算1-10的和返回
int sum = 0 ;
for(int i = 1 ; i <= 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+" => " + i);
sum+=i;
}
return Thread.currentThread().getName()+"执行的结果是:"+sum;
}
}
/**
目标:线程的注意事项。
1.线程的启动必须调用start()方法。否则当成普通类处理。
-- 如果线程直接调用run()方法,相当于变成了普通类的执行,此时将只有主线程在执行他们!
-- start()方法底层其实是给CPU注册当前线程,并且触发run()方法执行
2.建议线程先创建子线程,主线程的任务放在之后。否则主线程永远是先执行完!
*/
public class ThreadDemo {
// 启动这个类,这个类就是进程,它自带一个主线程,
// 是main方法,main就是一个主线程的执行!!
public static void main(String[] args) {
// 3.创建一个新的线程对象。
Thread t = new MyThread();
// 4. 调用线程对象的start()方法启动线程。最终还是执行run()方法!
// 如果线程直接调用run()方法,相当于变成了普通类的执行,此时将只有主线程在执行他们!此时其实还是只有一个线程就是主线程执行
// t.run();
// start()方法底层其实是给CPU注册当前线程,并且触发run()方法执行
t.start();
for(int i = 0 ; i < 10 ; i++ ) {
System.out.println("main线程输出:"+i);
}
}
}
// 1.定义一个线程类继承Thread。线程类并不是线程对象,用来创建线程对象的。
class MyThread extends Thread{
// 2.重写run()方法
@Override
public void run() {
for(int i = 0 ; i < 10 ; i++ ) {
System.out.println("子线程输出:"+i);
}
}
}
线程的常用API
/**
目标:线程的常用API.
Thread类的API:
1.public void setName(String name):给当前线程取名字。
2.public void getName():获取当前线程的名字。
-- 线程存在默认名称,子线程的默认名称是:Thread-索引。
-- 主线程的默认名称就是:main
3.public static Thread currentThread()
-- 获取当前线程对象,这个代码在哪个线程中,就得到哪个线程对象。
*/
public class ThreadDemo {
// 启动后的ThreadDemo当成一个进程。
// main方法是由主线程执行的,理解成main方法就是一个主线程
public static void main(String[] args) {
// 创建一个线程对象
Thread t1 = new MyThread();
t1.setName("1号线程");
t1.start();
//System.out.println(t1.getName()); // 获取线程名称
Thread t2 = new MyThread();
t2.setName("2号线程");
t2.start();
//System.out.println(t2.getName()); // 获取线程名称
// 主线程的名称如何获取呢?
// 这个代码在哪个线程中,就得到哪个线程对象。
Thread m = Thread.currentThread();
m.setName("最强线程main");
//System.out.println(m.getName()); // 获取线程名称
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(m.getName()+"==>"+i);
}
}
}
// 1.定义一个线程类继承Thread类。
class MyThread extends Thread{
// 2.重写run()方法
@Override
public void run() {
// 线程的执行方法。
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}
线程休眠API
/**
目标:线程休眠API.
public static void sleep(long time): 让当前线程休眠多少毫秒再继续执行。
*/
public class ThreadDemo02 {
public static void main(String[] args) {
for(int i = 0 ; i < 10 ; i++ ) {
System.out.println(i);
try {
// 项目经理让我加上这行代码
// 如果用户交钱了,我就去掉。
Thread.sleep(1000); // 让当前线程休眠1s.
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
通过Thread类的有参数构造器为当前线程对象取名字
package com.itheima._14线程的常用API;
/**
目标:通过Thread类的有参数构造器为当前线程对象取名字。
-- public Thread()
-- public Thread(String name):创建线程对象并取名字。
*/
public class ThreadDemo03 {
// 启动这个类,这个类就是进程,它自带一个主线程,
// 是main方法,main就是一个主线程的执行!!
public static void main(String[] args) {
Thread t1 = new MyThread02("1号线程");
t1.start();
Thread t2 = new MyThread02("2号线程");
t2.start();
Thread.currentThread().setName("主线程");
for(int i = 0 ; i < 10 ; i++ ) {
System.out.println(Thread.currentThread().getName()+" => "+i);
}
}
}
// 1.定义一个线程类继承Thread。线程类并不是线程对象,用来创建线程对象的。
class MyThread02 extends Thread{
public MyThread02(String name) {
// public Thread(String name):父类的有参数构造器
super(name); // 调用父类的有参数构造器初始化当前线程对象的名称!
}
// 2.重写run()方法
@Override
public void run() {
for(int i = 0 ; i < 10 ; i++ ) {
System.out.println(Thread.currentThread().getName()+" => "+i);
}
}
}
线程安全问题
/**
目标:线程安全问题。
线程安全问题:多个线程同时操作同一个共享资源的时候可能会出现线程安全问题。
模拟出取款问题的案例:
注意:用高度面向对象的思想设计。
分析:
(1)提供一个账户类Account.java作为创建共享资源账户对象的类。
(2)定义一个线程类来用于创建2个线程分别代表小明和小红来取钱。
小结:
多个线程同时操作同一个共享资源的时候可能会出现线程安全问题。
*/
public class ThreadSafe {
public static void main(String[] args) {
// a.创建一个共享资源账户对象!
Account acc = new Account("ICBC-110" , 100000);
// b.创建2个线程对象去账户对象中取钱
Thread littleMing = new DrawThread(acc,"小明");
littleMing.start();
Thread litterRed = new DrawThread(acc,"小红");
litterRed.start();
}
}
// 账户类
public class Account {
private String cardID;
private double moeny;
// 小明线程/小红线程
public void drawMoney(double moeny) {
// 开始判断取钱逻辑
// 1.先知道是谁来取钱
String name = Thread.currentThread().getName();
// 2.判断余额是否足够
if(this.moeny >= moeny){
System.out.println(name+"来取钱,余额足够,吐出"+moeny);
// 3.更新余额
this.moeny -= moeny;
System.out.println(name+"来取钱后,余额剩余"+ this.moeny);
}else{
System.out.println(name+"来取钱,余额不足!");
}
}
public Account() {
}
public Account(String cardID, double moeny) {
this.cardID = cardID;
this.moeny = moeny;
}
public String getCardID() {
return cardID;
}
public void setCardID(String cardID) {
this.cardID = cardID;
}
public double getMoeny() {
return moeny;
}
public void setMoeny(double moeny) {
this.moeny = moeny;
}
}
package com.itheima._17线程安全;
// 线程类:创建取钱线程对象的。
public class DrawThread extends Thread{
// 定义一个成员变量接收账户对象
private Account acc ;
public DrawThread(Account acc,String name){
super(name);
this.acc = acc;
}
@Override
public void run() {
// 小明 小红
// 取钱100000
acc.drawMoney(100000);
}
}
线程同步_同步代码块
/**
目标:线程同步_同步代码块
线程同步的作用:就是为了解决线程安全问题的方案。
线程同步解决线程安全问题的核心思想:
让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
线程同步的做法:加锁
是把共享资源进行上锁,每次只能一个线程进入访问完毕以后,其他线程才能进来。
线程同步的方式有三种:
(1)同步代码块。
(2)同步方法。
(3)lock显示锁。
a.同步代码块。
作用:把出现线程安全问题的核心代码给上锁,每次只能一个线程进入
执行完毕以后自动解锁,其他线程才可以进来执行。
格式:
synchronized(锁对象){
// 访问共享资源的核心代码
}
锁对象:理论上可以是任意的“唯一”对象即可。
原则上:锁对象建议使用共享资源。
-- 在实例方法中建议用this作为锁对象。此时this正好是共享资源!必须代码高度面向对象
-- 在静态方法中建议用类名.class字节码作为锁对象。
*/
public class ThreadSafe {
public static void main(String[] args) {
// 入口类:
// 1.创建一个共享的账户对象(共享资源),账户对象必须只创建一个。
Account acc = new Account(10000, "ICBC-110");
// 2.创建2个线程对象代表:小明和小红。
Thread xiaoMing = new DrawThread(acc,"小明");
xiaoMing.start();
Thread xiaoRed = new DrawThread(acc,"小红");
xiaoRed.start();
}
}
// 账户类
public class Account {
private double money ; // 余额
private String cardId ;
public Account(){
}
public Account(double money, String cardId) {
this.money = money;
this.cardId = cardId;
}
// 小明、小红执行到此
public void drawMoney(double money) {
// 1.先拿到是谁来取钱:取当前线程对象的名称
String name = Thread.currentThread().getName();
// 小明、小红
// this == 当前共享账户acc
// 2.判断余额是否足够
synchronized (this){
if(this.money >= money){
// 3.余额足够
System.out.println(name+"来取钱,吐出:"+money);
// 4.更新余额
this.money -= money;
// 5.输出结果
System.out.println(name+"来取钱"+money+"成功,取钱后剩余:"+this.money);
}else{
// 6.余额不足
System.out.println(name+"来取钱,余额不足,剩余"+this.money);
}
}
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
}
/**
取钱的线程类
*/
public class DrawThread extends Thread {
private Account acc ; // 定义了一个账户类型的成员变量接收取款的账户对象!
public DrawThread(Account acc , String name){
super(name); // 为当前线程对象取名字
this.acc = acc;
}
@Override
public void run() {
// 小明/小红
acc.drawMoney(10000);
}
}
线程同步_同步方法
/**
目标:线程同步_同步代码块
线程同步的作用:就是为了解决线程安全问题的方案。
线程同步解决线程安全问题的核心思想:
让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
线程同步的做法:
是把共享资源进行上锁,每次只能一个线程进入访问完毕以后,其他线程才能进来。
线程同步的方式有三种:
(1)同步代码块。
(2)同步方法。
(3)lock显示锁。
b.同步方法
作用:把出现线程安全问题的核心方法给锁起来,
每次只能一个线程进入访问,其他线程必须在方法外面等待。
用法:直接给方法加上一个修饰符 synchronized.
原理: 同步方法的原理和同步代码块的底层原理其实是完全一样的,只是
同步方法是把整个方法的代码都锁起来的。
同步方法其实底层也是有锁对象的:
如果方法是实例方法:同步方法默认用this作为的锁对象。
如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
*/
public class ThreadSafe {
public static void main(String[] args) {
// a.创建一个账户对象。
Account acc = new Account(10000,"ICBC-110" );
// b.定义一个线程类创建2个线程代表小明和小红
Thread xiaoMing = new DrawThread(acc , "小明");
xiaoMing.start();
Thread xiaoRed = new DrawThread(acc, "小红");
xiaoRed.start();
}
}
// 账户类
public class Account {
private double money ; // 余额
private String cardId ;
public Account(){
}
public Account(double money, String cardId) {
this.money = money;
this.cardId = cardId;
}
// 小明、小红执行到此
public synchronized void drawMoney(double money) {
// 1.先拿到是谁来取钱:取当前线程对象的名称
String name = Thread.currentThread().getName();
// 2.判断余额是否足够
if(this.money >= money){
// 3.余额足够
System.out.println(name+"来取钱,吐出:"+money);
// 4.更新余额
this.money -= money;
// 5.输出结果
System.out.println(name+"来取钱"+money+"成功,取钱后剩余:"+this.money);
}else{
// 6.余额不足
System.out.println(name+"来取钱,余额不足,剩余"+this.money);
}
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
}
/**
取钱的线程类
*/
public class DrawThread extends Thread {
private Account acc ; // 定义了一个账户类型的成员变量接收取款的账户对象!
public DrawThread(Account acc , String name){
super(name); // 为当前线程对象取名字
this.acc = acc ;
}
@Override
public void run() {
// 小明/小红
acc.drawMoney(10000);
}
}
Lock锁
/**
目标:线程同步_同步代码块
线程同步的作用:就是为了解决线程安全问题的方案。
线程同步解决线程安全问题的核心思想:
让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
线程同步的做法:
是把共享资源进行上锁,每次只能一个线程进入访问完毕以后,其他线程才能进来。
线程同步的方式有三种:
(1)同步代码块。
(2)同步方法。
(3)lock显示锁。
c.lock显示锁。
java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,
同步代码块/同步方法具有的功能Lock都有,除此之外更强大
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
- `public void lock() `:加同步锁。
- `public void unlock()`:释放同步锁。
总结:
线程安全,性能差。
线程不安全性能好。假如开发中不会存在多线程安全问题,建议使用线程不安全的设计类。
*/
public class ThreadSafe {
public static void main(String[] args) {
// a.创建一个账户对象。
Account acc = new Account(10000,"ICBC-110" );
// b.定义一个线程类创建2个线程代表小明和小红
Thread xiaoMing = new DrawThread(acc , "小明");
xiaoMing.start();
Thread xiaoRed = new DrawThread(acc, "小红");
xiaoRed.start();
}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// 账户类
public class Account {
private double money ; // 余额
private String cardId ;
// 创建一把锁对象:因为账户对象对于小明小红是唯一的,所以这里的锁对象对于小明小红也是唯一的
private final Lock lock = new ReentrantLock();
public Account(){
}
public Account(double money, String cardId) {
this.money = money;
this.cardId = cardId;
}
// 小明、小红执行到此
public void drawMoney(double money) {
// 1.先拿到是谁来取钱:取当前线程对象的名称
String name = Thread.currentThread().getName();
lock.lock(); // 上锁~!
try{
if(this.money >= money){
// 3.余额足够
System.out.println(name+"来取钱,吐出:"+money);
// 4.更新余额
this.money -= money;
// 5.输出结果
System.out.println(name+"来取钱"+money+"成功,取钱后剩余:"+this.money);
}else{
// 6.余额不足
System.out.println(name+"来取钱,余额不足,剩余"+this.money);
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock(); // 解锁~!
}
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
}
/**
取钱的线程类
*/
public class DrawThread extends Thread {
private Account acc ; // 定义了一个账户类型的成员变量接收取款的账户对象!
public DrawThread(Account acc , String name){
super(name); // 为当前线程对象取名字
this.acc = acc ;
}
@Override
public void run() {
// 小明/小红
acc.drawMoney(10000);
}
}
线程通信(了解原理,代码几乎不用)
/**
目标:线程通信(了解原理,代码几乎不用)
线程通信:多个线程因为在同一个进程中,所以互相通信比较容易的。
线程通信的经典模型:生产者与消费者问题。
生产者负责生成商品,消费者负责消费商品。
生产不能过剩,消费不能没有。
模拟一个案例:
小明和小红有一个共同账户:共享资源
他们有3个爸爸(亲爸,岳父,干爹)给他们存钱。
模型:小明和小红去取钱,如果有钱就取出,然后等待自己,唤醒他们3个爸爸们来存钱
他们的爸爸们来存钱,如果发现有钱就不存,没钱就存钱,然后等待自己,唤醒孩子们来取钱。
做整存整取:10000元。
分析:
生产者线程:亲爸,岳父,干爹
消费者线程:小明,小红
共享资源:账户对象。
注意:线程通信一定是多个线程在操作同一个资源才需要进行通信。
线程通信必须先保证线程安全,否则毫无意义,代码也会报错!
线程通信的核心方法:
public void wait(): 让当前线程进入到等待状态 此方法必须锁对象调用.
public void notify() : 唤醒当前锁对象上等待状态的某个线程 此方法必须锁对象调用
public void notifyAll() : 唤醒当前锁对象上等待状态的全部线程 此方法必须锁对象调用
小结:
是一种等待唤醒机制。
必须是在同一个共享资源才需要通信,而且必须保证线程安全。
*/
public class ThreadCommunication {
public static void main(String[] args) {
// 1.创建一个账户对象。
Account acc = new Account("ICBC-1313113",0);
// 2.创建2个取钱线程。
new DrawThread(acc , "小明").start();
new DrawThread(acc , "小红").start();
// 3.创建3个存钱线程。
new SaveThread(acc , "亲爹").start();
new SaveThread(acc , "干爹").start();
new SaveThread(acc , "岳父").start();
}
}
// 账户对象
public class Account {
private String cardId ;
private double money ; // 余额。
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
// 亲爸,干爹,岳父
public synchronized void saveMoney(double money) {
try{
// 1.知道是谁来存钱
String name = Thread.currentThread().getName();
// 2.判断余额是否足够
if(this.money > 0){
// 5.等待自己,唤醒别人!
this.notifyAll();
this.wait();
}else{
// 3.钱没有,存钱
this.money += money;
System.out.println(name+"来存钱,存入了"+money+"剩余:"+this.money);
// 4.等待自己,唤醒别人!
this.notifyAll();
this.wait();
}
}catch (Exception e){
e.printStackTrace();
}
}
// 小明 小红
public synchronized void drawMoney(double money) {
try{
// 1.知道是谁来取钱
String name = Thread.currentThread().getName();
// 2.判断余额是否足够
if(this.money > 0){
// 3.账户有钱,有钱可以取
this.money -= money;
System.out.println(name+"来取钱"+money+"取钱后剩余:"+this.money);
// 4.没钱,先唤醒别人,等待自己,。
this.notifyAll();
this.wait();
}else{
// 5.余额不足,没钱,先唤醒别人,等待自己,。
this.notifyAll();
this.wait();
}
}catch (Exception e){
e.printStackTrace();
}
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
/**
取钱的线程类
*/
public class DrawThread extends Thread {
private Account acc ; // 定义了一个账户类型的成员变量接收取款的账户对象!
public DrawThread(Account acc , String name){
super(name); // 为当前线程对象取名字
//this.acc = acc ;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(4000);
acc.drawMoney(10000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
存钱的线程类
*/
public class SaveThread extends Thread {
private Account acc ; // 定义了一个账户类型的成员变量接收取款的账户对象!
public SaveThread(Account acc , String name){
super(name); // 为当前线程对象取名字
this.acc = acc ;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(4000);
acc.saveMoney(10000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
线程池
/**
目标:什么是线程池。
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复的使用,
省去了频繁创建和销毁线程对象的操作,无需反复创建线程而消耗过多资源。
为什么要用线程池:
合理利用线程池能够带来三个好处
1.降低资源消耗。
-- 减少了创建和销毁线程的次数,每个工作线程都
可以被重复利用,可执行多个任务。
2.提高响应速度
-- 不需要频繁的创建线程,如果有
线程可以直接用,不会出现系统僵死!
3.提高线程的可管理性(线程池可以约束系统最多只能有多少个线程,
不会因为线程过多而死机)
线程池的核心思想:线程复用,同一个线程可以被重复使用,来处理多个任务。
*/
public class ThreadPoolsDemo01 {
}
Executors工具类构建线程池对象-提交Runnable任务
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
目标:创建一个线程池。
线程池在Java中的代表类:ExecutorService(接口)。
Java在Executors类下提供了一个静态方法得到一个线程池的对象:
1.public static ExecutorService newFixedThreadPool(int nThreads):
创建一个线程池返回。
ExecutorService提交线程任务对象执行的方法:
1.Future<?> submit(Runnable task):提交一个Runnable的任务对象给线程池执行。
小结:
pools.shutdown(); // 等待任务执行完毕以后才会关闭线程池
pools.shutdownNow(); // 立即关闭线程池的代码,无论任务是否执行完毕!
线程池中的线程可以被复用,线程用完以后可以继续去执行其他任务。
*/
public class ThreadPoolsDemo02 {
public static void main(String[] args) {
// a.创建一个线程池,指定线程的固定数量是3.
// new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
ExecutorService pools = Executors.newFixedThreadPool(3);
// b.创建线程的任务对象。
Runnable target = new MyRunnable();
// c.把线程任务放入到线程池中去执行。
pools.submit(target); // 提交任务,此时会创建一个新线程,自动启动线程执行!
pools.submit(target); // 提交任务,此时会创建一个新线程,自动启动线程执行!
pools.submit(target); // 提交任务,此时会创建一个新线程,自动启动线程执行!
pools.submit(target); // 不会再创建新线程,会复用之前的线程来处理这个任务
pools.shutdown(); // 等待任务执行完毕以后才会关闭线程池
//pools.shutdownNow(); // 立即关闭线程池的代码,无论任务是否执行完毕!
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0 ; i < 5 ; i++ ){
System.out.println(Thread.currentThread().getName()+" => "+i);
}
}
}
Executors工具类构建线程池对象-提交Callable任务
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
目标:创建一个线程池。
线程池在Java中的代表类:ExecutorService(接口)。
Java在Executors类下提供了一个静态方法得到一个线程池的对象:
1.public static ExecutorService newFixedThreadPool(int nThreads):
创建一个线程池返回。
ExecutorService提交线程任务对象执行的方法:
1.Future<?> submit(Runnable task):提交一个Runnable的任务对象给线程池执行。
1.Future<?> submit(Callable task):提交一个Runnable的任务对象给线程池执行。
小结:
Callable做线程池的任务,可以得到它执行的结果!!
*/
public class ThreadPoolsDemo03 {
public static void main(String[] args) {
// a.创建一个线程池,指定线程的固定数量是3.
// new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
ExecutorService pools = Executors.newFixedThreadPool(3);
Future<String> t1 = pools.submit(new MyCallable(10)); // 提交任务,此时会创建一个新线程,自动启动线程执行!
Future<String> t2 = pools.submit(new MyCallable(20)); // 提交任务,此时会创建一个新线程,自动启动线程执行!
Future<String> t3 = pools.submit(new MyCallable(30)); // 提交任务,此时会创建一个新线程,自动启动线程执行!
Future<String> t4 = pools.submit(new MyCallable(40)); // 复用之前的某个线程
try{
// b.可以得到线程池执行的任务结构
String rs1 = t1.get();
String rs2 = t2.get();
String rs3 = t3.get();
String rs4 = t4.get();
System.out.println(rs1);
System.out.println(rs2);
System.out.println(rs3);
System.out.println(rs4);
}catch (Exception e){
e.printStackTrace();
}
}
}
// 1.定义一个线程任务类实现Callable接口 , 申明线程执行的结果类型。
class MyCallable implements Callable<String>{
private int n;
public MyCallable(int n){
this.n = n;
}
// 2.重写线程任务类的call方法,这个方法可以直接返回执行的结果。
@Override
public String call() throws Exception {
int sum = 0 ;
for(int i = 1 ; i <= n ; i++){
System.out.println(Thread.currentThread().getName()+" => "+i);
sum += i ;
}
return Thread.currentThread().getName()+"计算1-"+n+"的和:"+sum;
}
}
手动创建线程池
import java.util.concurrent.*;
/**
目标:手动创建线程池对象:提供可配置的程池对象(编程规范,推荐使用)
* public ThreadPoolExecutor(int corePoolSize,
* int maximumPoolSize,
* long keepAliveTime,
* TimeUnit unit,
* BlockingQueue<Runnable> workQueue,
* ThreadFactory threadFactory,
* RejectedExecutionHandler handler)
*
* 参数一:核心线程数量: corePoolSize
* 参数二:能容纳最大线程数: maximumPoolSize
* 参数三:空闲线程最大存活时间: keepAliveTime
* 参数四:时间单位: unit
* 参数五:任务队列: workQueue
* 参数六:创建线程的工厂类对象: threadFactory
* 参数七:任务的拒绝策略: handler
* ExecutorService pools = new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,
* new ArrayBlockingQueue<>(6),
* Executors.defaultThreadFactory(),
* new ThreadPoolExecutor.AbortPolicy());
* 临时线程(5-3)的创建时机:
* 一个任务被提交到线程池以后,首先会找有没有空闲存活线程
* 如果有则直接将任务交给这个空闲线程来执行
* 如果没有则会缓存到工作队列中,如果工作队列的任务放满了,才会在创建一个新线程
* ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出RejectedExecutionException异常。是默认的策略
* ThreadPoolExecutor.DiscardPolicy 丢弃任务,但是不抛出异常 这是不推荐的做法
* ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务 然后把当前任务加入队列中
* ThreadPoolExecutor.CallerRunsPolicy 调用任务的run()方法绕过线程池直接执行
*/
public class ThreadpoolDemo03 {
public static void main(String[] args) {
// 手动创建线程池对象
ThreadPoolExecutor pools = new ThreadPoolExecutor(3, 5 ,
8 , TimeUnit.SECONDS , new ArrayBlockingQueue<>(6) ,
Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunnableMe();
pools.execute(target); // 1自动创建一个新线程 自动触发线程执行此任务
pools.execute(target); // 2自动创建一个新线程 自动触发线程执行此任务
pools.execute(target); // 3自动创建一个新线程 自动触发线程执行此任务
pools.execute(target); //4
pools.execute(target); //5
pools.execute(target); //6
pools.execute(target); //7
pools.execute(target); //8
pools.execute(target); //9
pools.execute(target); //10开始招临时工,跑1个任务!
pools.execute(target); //11开始招临时工,跑1个任务!
pools.execute(target); //12被拒绝的
}
}
死锁
/**
目标:死锁。
死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
由于线程被无限期地阻塞,因此程序不可能正常终止。
客户(占用资金,等待经销商的货品资源) 经销商(占用货品资源,等待客户的资金)
java 死锁产生的四个必要条件:
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用。
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待循环队列:p1要p2的资源,p2要p1的资源。这样就形成了一个等待环路
当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,
便可让死锁消失
代码实现。
小结:
死锁是多个线程满足上述四个条件才会形成,死锁需要尽量避免。
死锁一般存在资源的嵌套请求!
*/
public class ThreadDead {
// 1.至少需要两个资源,每个资源只需要1份。
public static Object resources1 = new Object();
public static Object resources2 = new Object();
public static void main(String[] args) {
// 2.创建2个线程。
new Thread(new Runnable() {
@Override
public void run() {
// 线程1:占用资源1 ,请求资源2
synchronized (resources1){
System.out.println("线程1已经占用了资源1,开始请求资源2");
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (resources2){
System.out.println("线程1已经占用了资源2");
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (resources2){
System.out.println("线程2已经占用了资源2,开始请求资源1");
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (resources1){
System.out.println("线程2已经占用了资源1");
}
}
}
}).start();
}
}
volatile关键字深入学习
/**
目标:volatile关键字深入学习。
引入:
问题:线程修改了某个成员变量的值,但是在主线程中读取到的还是之前的值
修改后的值无法读取到。
原因:按照JMM模型,所有的成员变量和静态变量都存在于主内存中,主内存中的变量可以被多个线程共享。
每个线程都存在一个专属于自己的工作内存,工作内存一开始存储的是成员变量的副本。
所以线程很多时候都是直接访问自己工作内存中的该变量,其他线程对主内存变量值的修改将不可见!!
解决此问题:
希望所有线程对于主内存的成员变量修改,其他线程是可见的。
(1)加锁:可以实现其他线程对变量修改的可见性。
某一个线程进入synchronized代码块前后,执行过程入如下:
a.线程获得锁
b.清空工作内存
c.从主内存拷贝共享变量最新的值到工作内存成为副本
(2)可以给成员变量加上一个volatile关键字,立即就实现了成员变量多线程修改的可见性。
小结:
可以给成员变量加上一个volatile关键字,当一个线程修改了这个成员变量的值,其他线程可以立即看到修改后的值并使用!
volatile与synchronized的区别。
- volatile只能修饰实例变量和静态变量,而synchronized可以修饰方法,以及代码块。
- volatile保证数据的可见性,但是不保证原子性(多线程进行写操作,不保证线程安全);
而synchronized是一种排他(互斥)的机制,
*/
public class VolatileDemo01 {
public static void main(String[] args) {
// 1.启动线程,把线程对象中的flag改为true。
VolatileThread t = new VolatileThread();
t.start();
// 2.定义一个死循环
while(true){
// 这里读取到了flag值一直是false,虽然线程已经把它的值改成了true。
if(t.isFlag()){
System.out.println("执行了循环一次~~~~~~~");
}
}
/* while(true){
synchronized ("ddd"){
// 这里读取到了flag值一直是false,虽然线程已经把它的值改成了true。
if(t.isFlag()){
System.out.println("执行了循环一次~~~~~~~");
}
}
}*/
}
}
// 线程类。
class VolatileThread extends Thread {
// 定义成员变量
// volatile可以实现变量一旦被子线程修改,其他线程可以马上看到它修改后的最新值!
private boolean flag = false ;
public boolean isFlag() {
return flag;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
// 将flag的值更改为true
this.flag = true ;
System.out.println("线程修改了flag=" + flag);
}
}
原子性
/**
目标:原子性。
概述:所谓的原子性是指在一次操作或者多次操作中,
所有的操作全部都得到了执行并且不会受到任何因素的干扰。最终结果要保证线程安全。
小结:在多线程环境下,volatile关键字可以保证共享数据的可见性,
但是并不能保证对数据操作的原子性(在多线程环境下volatile修饰的变量也是线程不安全的)。
volatile的使用场景
- 开关控制
利用可见性特点,控制某一段代码执行或者关闭(比如今天课程的第一个案例)。
- 多个线程操作共享变量,但是是有一个线程对其进行写操作,其他的线程都是读。此时加上更好,其他线程可以立即读取到最新值。
volatile不能保证变量操作的原子性(安全性)。
*/
public class VolatileAtomicThread implements Runnable {
// 定义一个int类型的遍历
private volatile int count = 0 ;
@Override
public void run() {
// 对该变量进行++操作,100次
for(int x = 0 ; x < 100 ; x++) {
count++ ;
System.out.println("count =========>>>> " + count);
}
}
}
class VolatileAtomicThreadDemo {
public static void main(String[] args) {
// 创建VolatileAtomicThread对象
Runnable target = new VolatileAtomicThread() ;
// 开启100个线程对执行这一个任务。
for(int x = 0 ; x < 100 ; x++) {
new Thread(target).start();
}
}
}
原子性问题的解决
/**
目标:问题解决。
如何保证变量访问的原子性呢?
1.加锁实现线程安全。
2.基于CAS方式的原子类。
*/
public class VolatileAtomicThread implements Runnable {
// 定义一个int类型的遍历
private volatile int count = 0 ;
@Override
public void run() {
// 对该变量进行++操作,100次
for(int x = 0 ; x < 100 ; x++) {
synchronized (this){
count++ ;
System.out.println("count =========>>>> " + count);
}
}
}
}
class VolatileAtomicThreadDemo {
public static void main(String[] args) {
// 创建VolatileAtomicThread对象
Runnable target = new VolatileAtomicThread() ;
// 开启100个线程对执行这一个任务。
for(int x = 0 ; x < 100 ; x++) {
new Thread(target).start();
}
}
}
原子性问题的解决02
import java.util.concurrent.atomic.AtomicInteger;
/**
目标:问题解决。
如何保证变量访问的原子性呢?
1.加锁实现线程安全。
-- 虽然安全性得到了保证,但是性能不好!!
2.基于CAS方式的原子类。
-- Java已经提供了一些本身即可实现原子性(线程安全)的类。
-- 概述:java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),
这个包中的原子操作类提供了一种用法简单
,性能高效,线程安全地更新一个变量的方式。
-- 操作整型的原子类
public AtomicInteger(): 初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
int get(): 获取值
int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
CAS与Synchronized总结:
CAS和Synchronized都可以保证多线程环境下共享数据的安全性。那么他们两者有什么区别?
Synchronized是从悲观的角度出发:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁
(**共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程**)。因此Synchronized我们也将其称之为**悲观锁**。jdk中的ReentrantLock也是一种悲观锁。性能较差!!
CAS是从乐观的角度出发:
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。
CAS这种机制我们也可以将其称之为乐观锁。综合性能较好!
*/
public class VolatileAtomicThread implements Runnable {
// 原子类中封装好了整型变量,默认值是0
private AtomicInteger atomicInteger = new AtomicInteger();
@Override
public void run() {
// 对该变量进行++操作,100次
for(int x = 0 ; x < 100 ; x++) {
int count = atomicInteger.incrementAndGet(); // 底层变量+1且返回!
System.out.println("count =========>>>> " + count);
}
}
}
class VolatileAtomicThreadDemo {
public static void main(String[] args) {
// 创建VolatileAtomicThread对象
Runnable target = new VolatileAtomicThread() ;
// 开启100个线程对执行这一个任务。
for(int x = 0 ; x < 100 ; x++) {
new Thread(target).start();
}
}
}
并发包的介绍。(面试的重点中的重点)
import java.util.Hashtable;
import java.util.Map;
/**
目标:并发包的介绍。(面试的重点中的重点)
并发包的来历:
在实际开发中如果不需要考虑线程安全问题,大家不需要做线程安全,因为如果做了反而性能不好!
但是开发中有很多业务是需要考虑线程安全问题的,此时就必须考虑了。否则业务出现问题。
Java为很多业务场景提供了性能优异,且线程安全的并发包,程序员可以选择使用!
Map集合中的经典集合:HashMap它是线程不安全的,性能好
-- 如果在要求线程安全的业务情况下就不能用这个集合做Map集合,否则业务会崩溃~
为了保证线程安全,可以使用Hashtable。注意:线程中加入了计时
-- Hashtable是线程安全的Map集合,但是性能较差!(已经被淘汰了,虽然安全,但是性能差)
为了保证线程安全,再看ConcurrentHashMap(不止线程安全,而且效率高,性能好,最新最好用的线程安全的Map集合)
-- ConcurrentHashMap保证了线程安全,综合性能较好!
小结:
HashMap是线程不安全的。
Hashtable线程安全基于synchronized,综合性能差,被淘汰了。
ConcurrentHashMap:线程安全的,分段式锁,综合性能最好,线程安全开发中推荐使用
*/
public class ConcurrentHashMapDemo {
// 定义一个静态的HashMap集合,只有一个容器。
// public static Map<String,String> map = new HashMap<>();
public static Map<String,String> map = new Hashtable<>();
//public static Map<String,String> map = new ConcurrentHashMap<>();
public static void main(String[] args) throws InterruptedException {
// HashMap线程不安全演示。
// 需求:多个线程同时往HashMap容器中存入数据会出现安全问题。
// 具体需求:提供2个线程分别给map集合加入50万个数据!
new AddMapDataThread().start();
new AddMapDataThread().start();
//休息10秒,确保两个线程执行完毕
Thread.sleep(1000 * 4);
//打印集合大小
System.out.println("Map大小:" + map.size());
}
}
class AddMapDataThread extends Thread{
@Override
public void run() {
for(int i = 0 ; i < 1000000 ; i++ ){
ConcurrentHashMapDemo.map.put(Thread.currentThread().getName()+"键:"+i , "值"+i);
}
}
}
CountDownLatch的使用
import java.util.concurrent.CountDownLatch;
/**
目标:CountDownLatch的使用。
CountDownLatch允许一个或多个线程等待其他线程完成操作,再执行自己。
例如:线程1要执行打印:A和C,线程2要执行打印:B,但线程1在打印A后,
要线程2打印B之后才能打印C,所以:线程1在打印A后,
必须等待线程2打印完B之后才能继续执行
需求:
提供A线程,打印 A , C
提供B线程,打印 B
构造器:
public CountDownLatch(int count)// 初始化唤醒需要的down几步。
方法:
public void await() throws InterruptedException// 让当前线程等待,必须down完初始化的数字才可以被唤醒,否则进入无限等待
public void countDown() // 计数器进行减1 (down 1)
小结:
CountDownLatch可以用于让某个线程等待几步才可以继续执行,
从而可以实现控制线程执行的流程!
*/
public class CountDownLatchDemo {
public static void main(String[] args) {
//创建1个计数器:用来控制 A , B线程的执行流程的。
CountDownLatch down = new CountDownLatch(1);
new ThreadA(down).start();
new ThreadB(down).start();
}
}
class ThreadA extends Thread{
private CountDownLatch down;
public ThreadA(CountDownLatch down){
this.down = down;
}
@Override
public void run() {
System.out.println("A");
try {
down.await(); // A线程你进入等待,让B线程执行自己!
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("C");
}
}
class ThreadB extends Thread{
private CountDownLatch down;
public ThreadB(CountDownLatch down){
this.down = down;
}
@Override
public void run() {
System.out.println("B");
down.countDown(); // 这里相当于是-1,代表自己执行完毕了。A线程被唤醒!!
}
}
并发包_CyclicBarrier
import java.util.concurrent.CyclicBarrier;
/**
目标: CyclicBarrier
CyclicBarrier作用:
某个线程任务必须等待其他线程执行完毕以后才能最终触发自己执行。
例如:公司召集5名员工开会,等5名员工都到了,会议开始。
我们创建5个员工线程,1个开会任务,几乎同时启动
使用CyclicBarrier保证5名员工线程全部执行后,再执行开会线程。
构造器:
public CyclicBarrier(int parties, Runnable barrierAction)
// 用于在线程到达屏障5时,优先执行barrierAction,方便处理更复杂的业务场景
方法:
public int await()
// 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞
小结:
可以实现多线程中,某个任务在等待其他线程执行完毕以后触发。
循环屏障可以实现达到一组屏障就触发一个任务执行!
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
// 1.创建一个任务循环屏障对象。
/**
* 参数一:代表多少个线程的执行。
* 参数二:到达执行屏障就开始触发的线程任务。
*/
CyclicBarrier cb = new CyclicBarrier(5 , new MeetingRunnable());
new PeopleThread(cb).start();
new PeopleThread(cb).start();
new PeopleThread(cb).start();
new PeopleThread(cb).start();
new PeopleThread(cb).start();
new PeopleThread(cb).start();
new PeopleThread(cb).start();
new PeopleThread(cb).start();
new PeopleThread(cb).start();
new PeopleThread(cb).start();
}
}
// 任务类:开始开会的任务
class MeetingRunnable implements Runnable{
@Override
public void run() {
System.out.println("人员到齐了开始由"+Thread.currentThread().getName()+"主持会议!");
}
}
// 员工类
class PeopleThread extends Thread{
private CyclicBarrier cb ;
public PeopleThread(CyclicBarrier cb) {
this.cb = cb;
}
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("员工:"+Thread.currentThread().getName()+"进入会议室");
cb.await(); // 自己做完了,告诉循环屏障我结束了!
} catch (Exception e) {
e.printStackTrace();
}
}
}
Semaphore的使用介绍
import java.util.concurrent.Semaphore;
/**
目标:Semaphore的使用介绍。
引入:
Semaphore(发信号)的主要作用是控制线程的并发数量。
synchronized可以起到"锁"的作用,但某个时间段内,只能有一个线程允许执行。
Semaphore可以设置同时允许几个线程执行。
Semaphore字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。
Semaphore的构造器:
public Semaphore(int permits): permits 表示许可线程的数量
public Semaphore(int permits, boolean fair):fair 表示公平性,如果这个设为 true 的话,下次执行的线程会是等待最久的线程
Semaphore的方法:
public void acquire() throws InterruptedException 表示获取许可
public void release() release() 表示释放许可
小结:
Semaphore可以控制并发线程同时进行的数量。
*/
public class SemaphoreDemo {
public static void main(String[] args) {
Service service = new Service();
for(int i = 1 ; i <= 5 ; i++ ){
new MyThread(service,"线程:"+i).start();
}
}
}
// 执行的任务。
class Service{
// 可以同时支持多个线程进入共享资源区执行。
private Semaphore semaphore = new Semaphore(2);
public void showMethod(){
try {
semaphore.acquire();
long startTimer = System.currentTimeMillis();
System.out.println("进入时间:"+startTimer);
System.out.println(Thread.currentThread().getName()+"进入资源执行");
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
long endTimer = System.currentTimeMillis();
System.out.println("结束时间:"+endTimer);
semaphore.release();
//acquire()和release()方法之间的代码为"同步代码"
}
}
// 线程类。
class MyThread extends Thread{
private Service service;
public MyThread(Service service , String name){
super(name);
this.service = service;
}
@Override
public void run() {
service.showMethod();
}
}
并发包_Exchanger
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
/**
目标:Exchanger
作用
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。
这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
Exchanger构造方法:
public Exchanger()
Exchanger重要方法:
public V exchange(V x)
分析:
(1)需要2个线程
(2)需要一个交换对象负责交换两个线程执行的结果。
小结:
Exchanger可以实现线程间的数据交换。
一个线程如果等不到对方的数据交换就会一直等待。
我们也可以控制一个线程等待的时间。
必须双方都进行交换才可以正常进行数据的交换。
*/
public class ExchangerDemo {
public static void main(String[] args) {
// 创建交换对象(信使)
Exchanger<String> exchanger = new Exchanger<>();
// 创建2给线程对象。
new ThreadA(exchanger).start();
new ThreadB(exchanger).start();
}
}
class ThreadA extends Thread{
private Exchanger<String> exchanger;
public ThreadA(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
try {
// 礼物A
System.out.println("线程A,做好了礼物A,等待线程B送来的礼物B.....");
// 开始交换礼物。参数是送给其他线程的礼物!
// System.out.println("线程A收到线程B的礼物:"+exchanger.exchange("礼物A"));
// 如果等待了5s还没有交换它就去死(抛出异常)!
System.out.println("线程A收到线程B的礼物:"+exchanger.exchange("礼物A", 5 , TimeUnit.SECONDS));
} catch (Exception e) {
System.out.println("线程A等待了5s,没有收到礼物,最终就执行结束了!");
}
}
}
class ThreadB extends Thread{
private Exchanger<String> exchanger;
public ThreadB(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
try {
// 礼物B
// System.out.println("线程B,做好了礼物B,等待线程A送来的礼物A.....");
// 开始交换礼物。参数是送给其他线程的礼物!
// System.out.println("线程B收到线程A的礼物:"+exchanger.exchange("礼物B"));
} catch (Exception e) {
e.printStackTrace();
}
}
}