多线程
1、Thread
1、自定义线程类继承Thread类
2、重写run()方法
3、创建线程对象,调用start()方法启动线程
package 多线程;
public class TestThread01 extends Thread{
@Override
public void run() {//run()方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我夹在主线程中间");
}
}
public static void main(String[] args) {//main()方法,主线程
//创建线程对象
TestThread01 testThread01 = new TestThread01();
//调用stars()方法启动线程
testThread01.start();
for (int i = 0; i < 2000; i++) {
System.out.println("我是主线程");
}
}
}
创建的线程会与main方法主线程同时执行,导致两条线程穿插出现!
线程同时执行时的顺序是由CPU来决定的,所以每一次执行的结果会不一样
package 多线程;
public class TestThread01 extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("我夹在主线程中间");
}
}
public static void main(String[] args) {
//创建线程对象
TestThread01 testThread01 = new TestThread01();
//调用stars()方法启动线程
testThread01.run();
for (int i = 0; i < 5; i++) {
System.out.println("我是主线程");
}
}
}
调用run()方法,先执行run()方法,再执行main()方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TtbaYBeb-1622943006575)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210601190453045.png)]
用多线程同时下载图片
package 多线程;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
//练习Thread,多线程下载图片
public class TestThread02 extends Thread{
private String url;//图片地址
private String name;//保存的文件名
public TestThread02(String url, String name)
{
this.name = name;
this.url = url;
}
@Override
public void run() {
WindowDownload download = new WindowDownload();
download.downloader(url, name);
System.out.println("下载了名为"+name+"文件");
}
public static void main(String[] args) {
TestThread02 t1 = new TestThread02("https://th.bing.com/th/id/R33674725d9ae34f86e3835ae30b20afe?rik=Pb3C9e5%2b%2b3a9Vw&riu=http%3a%2f%2fwww.desktx.com%2fd%2ffile%2fwallpaper%2fscenery%2f20180626%2f4c8157d07c14a30fd76f9bc110b1314e.jpg&ehk=9tpmnrrRNi0eBGq3CnhwvuU8PPmKuy1Yma0zL%2ba14T0%3d&risl=&pid=ImgRaw.jpg", "t1.jpg");
TestThread02 t2 = new TestThread02("https://cn.bing.com/images/search?view=detailV2&ccid=FaG6dzoh&id=E4CFCEE0D7CAE7BDCFCD4EEF7C4845CB3A245C87&thid=OIP.FaG6dzohGs3q45-DwsEyQQHaEK&mediaurl=https%3a%2f%2fth.bing.com%2fth%2fid%2fR15a1ba773a211acdeae39f83c2c13241%3frik%3dh1wkOstFSHzvTg%26riu%3dhttp%253a%252f%252fwww.desktx.com%252fd%252ffile%252fwallpaper%252fscenery%252f20170120%252ffdf948c82074494a74bf258eed4f855d.jpg%26ehk%3dfYrgVtm0hD0sZn455mcVyf5k%252bQz7RScjHLMyUi6jG0A%253d%26risl%3d%26pid%3dImgRaw&exph=945&expw=1680&q=%e5%9b%be%e7%89%87&simid=608028886585993196&ck=D470F24E5A731FA4B7F4E96539D01A75&selectedIndex=2&FORM=IRPRST","t2.jpg");
TestThread02 t3 = new TestThread02("https://tse4-mm.cn.bing.net/th/id/OIP.uPx-FbZ4nf2U05kCoJ7_1QHaE8?w=267&h=180&c=7&o=5&dpr=1.25&pid=1.7","t3.jpg");
t1.start();
t2.start();
t3.start();
}
}
class WindowDownload
{
public void downloader(String url, String name)
{
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QTmtNmpk-1622943006577)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210601194957153.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LhL31R9T-1622943006579)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210601195015029.png)]
name起名时为: ***.jpg作为图片文件
2、Runnable
1、自定义MyRunnable类实现Runnable接口
2、重写run()方法
3、创建线程对象
4、将Runnable对象传给Thread();
5、调用start()方法启动线程
package 多线程;
public class TestRunnable01 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我夹在主线程之前");
}
}
public static void main(String[] args) {
TestRunnable01 runnable01 = new TestRunnable01();
//创建线程对象,通过线程对象来开启我们的线程,代理
new Thread(runnable01 ).start();
//等价于
//Thread t1 = new Thread(runnable01 );
//t1.start();
for (int i = 0; i < 1000; i++) {
System.out.println("我是主线程");
}
}
}
使用Runnable接口模拟买票:
package 多线程;
//模拟车票购买
public class TestRunnable02 implements Runnable{
private int ticketNums = 10; //票数
@Override
public void run() {
while(true)
{
if (ticketNums <= 0)
{
break;
}
System.out.println(Thread.currentThread().getName()+"拿了第"+ticketNums--+"张票");
}
}
public static void main(String[] args) {
TestRunnable02 ticket = new TestRunnable02();
new Thread(ticket, "小猪").start();
new Thread(ticket, "小燕").start();
new Thread(ticket, "黄牛").start();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eZ8Vk83J-1622943006582)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210602144445150.png)]
增加模拟延时,使得发票较为均匀
package 多线程;
//模拟车票购买
public class TestRunnable02 implements Runnable{
private int ticketNums = 10; //票数
@Override
public void run() {
while(true)
{
if (ticketNums <= 0)
{
break;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿了第"+ticketNums--+"张票");
}
}
public static void main(String[] args) {
TestRunnable02 ticket = new TestRunnable02();
new Thread(ticket, "小猪").start();
new Thread(ticket, "小燕").start();
new Thread(ticket, "黄牛").start();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zlnVNN3w-1622943006585)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210602144710578.png)]
问题:增加模拟延时之后便出现了不同人拿了相同票的情况
结论:多个线程操纵同一个资源的情况下,线程不安全,会出现数据紊乱【将在线程同步阶段得以解决】
用多线程模拟龟兔赛跑
package 多线程;
//模拟龟兔赛跑
public class GTrace implements Runnable{
private static String winner;//胜利者只有一个
@Override
public void run() {
for (int i = 0; i < 1001; i++) {
//模拟兔子偷懒
if (Thread.currentThread().getName().equals("兔子") && i==199)
{//让兔子在第599步的时候睡一下
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (win(i))
{//如果比赛结束,停止程序
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
}
}
//判断比赛结束
public boolean win(int step)
{
if (winner != null)
{
return true;
}
else if (step >= 1000)
{
winner = Thread.currentThread().getName();
System.out.println("胜利者是"+winner);
return true;
}
return false;
}
public static void main(String[] args) {
GTrace race = new GTrace();//创建赛道
//乌龟和兔子在同一条赛道上比赛
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
3、Callable
package 多线程;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
public class TestCallable01 implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
WindowDownload download = new WindowDownload();
download.downloader(url, name);
System.out.println("下载了名为"+name+"文件");
return true;
}
private String url;//图片地址
private String name;//保存的文件名
public TestCallable01(String url, String name)
{
this.name = name;
this.url = url;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable01 t1 = new TestCallable01("https://th.bing.com/th/id/R33674725d9ae34f86e3835ae30b20afe?rik=Pb3C9e5%2b%2b3a9Vw&riu=http%3a%2f%2fwww.desktx.com%2fd%2ffile%2fwallpaper%2fscenery%2f20180626%2f4c8157d07c14a30fd76f9bc110b1314e.jpg&ehk=9tpmnrrRNi0eBGq3CnhwvuU8PPmKuy1Yma0zL%2ba14T0%3d&risl=&pid=ImgRaw.jpg", "t1.jpg");
TestCallable01 t2 = new TestCallable01("https://cn.bing.com/images/search?view=detailV2&ccid=FaG6dzoh&id=E4CFCEE0D7CAE7BDCFCD4EEF7C4845CB3A245C87&thid=OIP.FaG6dzohGs3q45-DwsEyQQHaEK&mediaurl=https%3a%2f%2fth.bing.com%2fth%2fid%2fR15a1ba773a211acdeae39f83c2c13241%3frik%3dh1wkOstFSHzvTg%26riu%3dhttp%253a%252f%252fwww.desktx.com%252fd%252ffile%252fwallpaper%252fscenery%252f20170120%252ffdf948c82074494a74bf258eed4f855d.jpg%26ehk%3dfYrgVtm0hD0sZn455mcVyf5k%252bQz7RScjHLMyUi6jG0A%253d%26risl%3d%26pid%3dImgRaw&exph=945&expw=1680&q=%e5%9b%be%e7%89%87&simid=608028886585993196&ck=D470F24E5A731FA4B7F4E96539D01A75&selectedIndex=2&FORM=IRPRST","t2.jpg");
TestCallable01 t3 = new TestCallable01("https://tse4-mm.cn.bing.net/th/id/OIP.uPx-FbZ4nf2U05kCoJ7_1QHaE8?w=267&h=180&c=7&o=5&dpr=1.25&pid=1.7","t3.jpg");
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);//需要三个执行池
//提交执行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
Future<Boolean> r3 = ser.submit(t3);
//获取结果
boolean s1 = r1.get();
boolean s2 = r2.get();
boolean s3 = r3.get();
//关闭服务
ser.shutdownNow();
}
}
class WindowDownload1
{
public void downloader(String url, String name)
{
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BjQ5xdLa-1622943006587)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210602153039457.png)]
使用Callable的好处:
1、可以定义返回值
2、可以抛出异常
4、静态代理
1、真实对象和代理对象要实现同一个接口
2、代理对象要代理真实对象
package 多线程;
public class StaticPoxy {
public static void main(String[] args) {
WeddingCompany weddingCompany = new WeddingCompany(new xiaozhu());
weddingCompany.HappyMarry();
}
}
interface Marry
{
void HappyMarry();
}
//真实对象
class xiaozhu implements Marry
{
@Override
public void HappyMarry() {
System.out.println("小猪要结婚了!");
}
}
//代理结婚
class WeddingCompany implements Marry
{
private Marry target;//代理对象
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
target.HappyMarry();//真实结婚的人
after();
}
private void after() {
System.out.println("结婚之后,收取尾款");
}
private void before() {
System.out.println("结婚之前,布置现场");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZkXEeuKd-1622943006588)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210602154919745.png)]
好处:1、代理对象可以做很多真实对象做不了的事情
2、真实对象只需要做好自己的事情
5、线程状态
6、线程方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pxvj5388-1622943006589)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210602185704914.png)]
1、线程停止
让线程停止最好自己编写方法stop(),不要用过时或者JDK不建议使用的方法
package 多线程;
//测试线程停止
//建议线程正常停止,利用次数,不建议死循环
//建议使用标志位
//不要使用stop或者destroy等过时或者JDK不建议使用的方法
public class TestThreadStop implements Runnable{
//设置标志位
private boolean flag;
//创建Stop方法
public void Stop()
{
this.flag = false;
}
@Override
public void run() {
flag = true;
int i = 0;
while (flag)
{
System.out.println("Thread线程" + i++);
}
}
public static void main(String[] args) {
TestThreadStop threadStop = new TestThreadStop();
new Thread(threadStop).start();
for (int i = 0; i < 300; i++) {
System.out.println(i+"main线程");
if(i == 250)
{
threadStop.Stop();
System.out.println("Thread线程停止");
}
}
}
}
2、线程休眠——sleep
1、模拟网络延时
package 多线程;
//模拟车票购买
public class TestRunnable02 implements Runnable{
private int ticketNums = 10; //票数
@Override
public void run() {
while(true)
{
if (ticketNums <= 0)
{
break;
}
System.out.println(Thread.currentThread().getName()+"拿了第"+ticketNums--+"张票");
}
}
public static void main(String[] args) {
TestRunnable02 ticket = new TestRunnable02();
new Thread(ticket, "小猪").start();
new Thread(ticket, "小燕").start();
new Thread(ticket, "黄牛").start();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3SBlriLk-1622943006590)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210602144710578.png)]
网络延时导致数据紊乱,不同人拿了同一张票
2、模拟倒计时
package 多线程;
public class TestTimeDown {
public static void TenDown()
{
int i = 10;
while(true)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i--);
if (i <= 0)
{
break;
}
}
}
public static void main(String[] args) {
TenDown();
}
}
结果:一秒输出一个数字
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EzTHJVym-1622943006591)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210602192451281.png)]
3、打印系统当时时间
public static void main(String[] args) {
Date starttime = new Date(System.currentTimeMillis());//获取系统当前时间
while(true) {
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(starttime));
starttime = new Date(System.currentTimeMillis());//更新系统当前时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:一秒打印一次时间,更新一次时间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5E6rTDAz-1622943006592)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210602193200710.png)]
3、线程礼让——yield
package 多线程;
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable
{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();//暂停执行,礼让
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
}
若注释掉11行代码“ Thread.yield();”,线程正常执行或者礼让不成功结果为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FklpEusV-1622943006593)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210604190957162.png)]
礼让成功,输出结果为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zy0z1cTU-1622943006594)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210604191048914.png)]
4、线程强制执行——join
Join合并线程,待该线程执行完之后,才能执行其他线程(可以想象成“插队”)
package 多线程;
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("VIP线程来了" + i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 30; i++) {
if (i == 10)
{
thread.join();
}
System.out.println("主线程" + i);
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w6JWB4Xn-1622943006595)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210604192339213.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sr4f5DWP-1622943006596)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210604192352504.png)]
5、线程状态观测
package 多线程;
public class TestThreadState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);//1s执行一次
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("");
}
});
//刚创建后
Thread.State state = thread.getState(); //获取线程当前状态
System.out.println(state); //new
//线程启动后
thread.start();
state = thread.getState();//更新state
System.out.println(state);
while(state != Thread.State.TERMINATED)
{//只要线程未停止就一直输出其状态
Thread.sleep(100);
state = thread.getState();//更新state
System.out.println(state);
}
}
}
输出结果:
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TERMINATED
6、线程优先级——Priority
package 多线程;
public class TestPriority {
public static void main(String[] args) {
//先输出主线程优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority priority = new MyPriority();
Thread t1 = new Thread(priority);
Thread t2 = new Thread(priority);
Thread t3 = new Thread(priority);
Thread t4 = new Thread(priority);
Thread t5 = new Thread(priority);
Thread t6 = new Thread(priority);
//线程优先级的设置要在,线程执行之前
t1.start();
t2.setPriority(10);
t2.start();
t3.setPriority(7);
t3.start();
t4.setPriority(6);
t4.start();
t5.setPriority(2);
t5.start();
t6.setPriority(8);
t6.start();
}
}
class MyPriority implements Runnable
{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-coD2wmtW-1622943006597)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210604195315601.png)]
7、守护线程——daemon
package 多线程;
public class TestDaemon {
public static void main(String[] args) {
GOD god = new GOD();
YOU you = new YOU();
Thread thread = new Thread(god);
thread.setDaemon(true);//因为上帝为守护线程,虚拟机不会管其什么时候结束
thread.start();
new Thread(you).start();//你为用户线程,其结束虚拟机便停止运行
}
}
//上帝
class GOD implements Runnable
{
@Override
public void run() {
while(true){
System.out.println("上帝与你同在!");
}
}
}
//你
class YOU implements Runnable
{
@Override
public void run() {
for (int i = 0; i < 365; i++) {
System.out.println("这是我的第"+(i+1)+"个生日");
}
System.out.println("++++再见世界++++++");
}
}
7、线程同步
并发:同一个对象被多个线程同时操作
处理多线程时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程在使用
形成条件:队列加锁
由于同一个进程的多个线程共享同意开存储空间,在带来方便的同时,也带来了访问冲突问题,未来保证数据在方法中被访问时的正确性,在访问时加入锁机制(synchronized),当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
1、一个线程持有锁会导致其他所有需要此锁的线程挂起
2、在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
3、如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题
1、未同步
模拟买票(不安全)
package 多线程;
public class TestBuyTicket {
public static void main(String[] args) {
Station station = new Station();
new Thread(station,"小红").start();
new Thread(station,"小明").start();
new Thread(station,"黄牛").start();
}
}
//定义车站
class Station implements Runnable {
private static int ticketnum = 10;
private static boolean flag = true;
@Override
public void run() {
while (flag)
{
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//定义买票方法
private void buy() throws InterruptedException {
if (ticketnum <= 0)
{
flag = false;
return;
}
//模拟延时
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketnum--+"张票");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D0gjFFRs-1622943006598)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210605085605362.png)]
问题:不同人抢到了同一张票
模拟银行取钱(不安全)
package 多线程;
public class TestBank {
public static void main(String[] args) {
Account account = new Account(100,"养老金");
Bank father = new Bank(account,50,"father");
Bank son = new Bank(account,100,"son");
son.start();
father.start();
}
}
class Account
{
public int money;
public String name;
public Account(int money, String name)
{
this.money = money;
this.name = name;
}
}
class Bank extends Thread
{
Account account;
String name;
int drawingMoney;
public Bank(Account account, int drawingMoney,String name)
{
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
if(account.money - drawingMoney < 0)
{
System.out.println(this.getName()+"余额不足");
return;
}
//模拟延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - drawingMoney;
System.out.println(this.getName()+"还剩"+account.money);
System.out.println(this.getName()+"取了"+drawingMoney);
}
}
问题:取出的钱加起来比原先账户余额多
使用线程给列表插入元素(不安全)
package 多线程;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class TestThreadList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
//添加延时,防止线程结束太快,list.size计算不及时
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VSOmcuO9-1622943006599)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210605093038843.png)]
问题:不足10000个元素
2、同步化(同步块:synchronized(Obj){})
同步块:synchronized(Obj){}
Obj称之为同步监视器
Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法中的同步监视器就是this,就是这个方法所处的对象本身,或者是class
同步监视器的执行过程
1、第一个线程访问,锁定同步监视器,执行其中代码
2、第二个线程访问,发现同步监视器被锁定,无法访问
3、第一个线程访问完毕,解锁同步监视器
4、第二个线程访问,发现同步监视器没有锁,然后锁定并访问
模拟买票(安全)
package 多线程;
public class TestBuyTicket {
public static void main(String[] args) {
Station station = new Station();
new Thread(station,"小红").start();
new Thread(station,"小明").start();
new Thread(station,"黄牛").start();
}
}
//定义车站
class Station implements Runnable {
private static int ticketnum = 10;
private static boolean flag = true;
@Override
public void run() {
while (flag)
{
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//定义买票方法
private synchronized void buy() throws InterruptedException {//run方法属于同步方法
if (ticketnum <= 0)
{
flag = false;
return;
}
//模拟延时
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketnum--+"张票");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4EOWX5qY-1622943006600)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210605093951578.png)]
模拟银行(安全)
package 多线程;
public class TestBank {
public static void main(String[] args) {
Account account = new Account(100,"养老金");
Bank father = new Bank(account,50,"father");
Bank son = new Bank(account,100,"son");
son.start();
father.start();
}
}
class Account
{
public int money;
public String name;
public Account(int money, String name)
{
this.money = money;
this.name = name;
}
}
class Bank extends Thread
{
Account account;
String name;
int drawingMoney;
public Bank(Account account, int drawingMoney,String name)
{
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
//锁的对象是变化的量
synchronized (account)//多线程修改的是account类,不是run方法本身的类Bank,所以采用synchronized (Obj){方法体}
{
if(account.money - drawingMoney < 0)
{
System.out.println(this.getName()+"余额不足");
return;
}
//模拟延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - drawingMoney;
System.out.println(this.getName()+"取了"+drawingMoney);
System.out.println("余额还剩:"+account.money);
}
}
}
注意synchronized针对的是被需要被多个线程所修改的类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aWfomISB-1622943006601)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210605094354993.png)]
插入列表元素(安全)
package 多线程;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class TestThreadList {
public static void main(String[] args) {
List<String> l = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (l){
l.add(Thread.currentThread().getName());
}
}).start();
}
//添加延时,防止线程结束太快,list.size计算不及时
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(l.size());
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mjf0YYzh-1622943006602)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210605094931268.png)]
3、JUC(java.util.concurrent.)(自带锁)
package 多线程;
import java.util.concurrent.CopyOnWriteArrayList;
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
4、死锁问题
多个线程各自占有一些共享资源,并且相互等待其他线程占有的资源释放后才能运行,而导致两个或者多个(所有)线程都在等待其他线程释放资源,而都停止执行的情况。某一个同步块同时拥有”两个以上对象的锁“时,就可能会发生”死锁“的问题。
package 多线程;
public class TestDeadLock {
public static void main(String[] args) {
Makeup makeup1 = new Makeup(1, "白雪公主");//白雪公主先拿镜子
Makeup makeup2 = new Makeup(2, "灰姑娘");//灰姑娘先拿口红
makeup2.start();
makeup1.start();
}
}
class Lipstick
{
}
class Mirror
{
}
class Makeup extends Thread
{
//确保口红和镜子都只有一份,使用static
static Mirror mirror = new Mirror();
static Lipstick lipstick = new Lipstick();
int choice;
String girlname;
Makeup(int choice, String girlname)
{
this.choice = choice;
this.girlname = girlname;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void makeup() throws InterruptedException {
if (choice == 1)//先拿镜子
{
synchronized (mirror)
{//获得镜子,并上锁
System.out.println(girlname+"获得了镜子");
Thread.sleep(2000);
synchronized (lipstick)
{//两秒后想要获得口红
System.out.println(girlname+"获得了口红");
}
}
}else{//先拿口红
synchronized (lipstick)
{//获得口红,并上锁
System.out.println(girlname+"获得了口红");
Thread.sleep(3000);
synchronized (mirror)
{//三秒后想要获得镜子
System.out.println(girlname+"获得了镜子");
}
}
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rsBPtQFj-1622943006603)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210605102546905.png)]
问题:线程无法正常结束
产生死锁的四个必要条件
1、互斥条件:一个资源每次只能被一个进程使用
2、请求与保持条件:一个进程因请求资源而阻塞时,对以获得的资源保持不放
3、不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
死锁避免方法:破掉上述其中一个或者多个条件即可
5、Lock(锁)
package 多线程;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
MyLock myLock = new MyLock();
new Thread(myLock).start();
new Thread(myLock).start();
new Thread(myLock).start();
}
}
class MyLock implements Runnable
{
int ticketnum = 10;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true)
{
try{
lock.lock();
if (ticketnum > 0)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketnum--);
}else {
break;
}
}finally {
lock.unlock();
}
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zfbfq7yJ-1622943006605)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210605104211525.png)]
synchronized与Lock对比
Lock是显式锁(需要手动开启和关闭锁,千万别忘记开锁),synchronized是隐式锁,除了作用域自动释放
Lock只有代码块锁,synchronized有代码块锁和方法所
使用Lock锁,JWM将花费较少的时间来调度线程(性能更好,并且由更好的扩展性,提供更多的子类)
优先使用顺序:
Lock>同步代码块(已经进入方法体,分配了相应资源)>同步方法(在方法体之外)
8、线程协作——wait(),notifyAll()
1、生产者消费者问题(管程法)
package 多线程;
public class TestPC {
public static void main(String[] args) {
SafeContainer container = new SafeContainer();
new Product(container).start();
new Consumer(container).start();
}
}
//生产者
class Product extends Thread
{
SafeContainer container;
public Product(SafeContainer container)
{
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了"+i+"只鸡");
}
}
}
//消费者
class Consumer extends Thread
{
SafeContainer container;
public Consumer(SafeContainer container)
{
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了第"+container.pop().id+"只鸡");
}
}
}
//产品
class Chicken
{
int id;//产品编号
public Chicken(int id)
{
this.id = id;
}
}
//缓冲区
class SafeContainer
{
//设置容器大小
Chicken[] chickens = new Chicken[10];
int count = 0;
//生产者生产产品
public synchronized void push(Chicken chicken)
{
if (count == chickens.length)
{//容器满了,等待消费者消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有满,继续生产
chickens[count] = chicken;
count++;
//通知消费者消费
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop()
{
if(count == 0)
{//没鸡了,等待生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有鸡,消费
Chicken chicken = chickens[count];
count--;
//通知生产者生产
this.notifyAll();
return chicken;
}
}
2、生产者消费者问题2(信号灯法)
package 多线程;
public class TestPC02 {
public static void main(String[] args) {
TV tv = new TV();
Actor actor = new Actor(tv);
Watcher watcher = new Watcher(tv);
actor.start();
watcher.start();
}
}
//定义演员类
class Actor extends Thread
{
TV tv;
public Actor(TV tv)
{
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2 == 0)
{
tv.play("开心宝贝");
}else {
tv.play("做好防疫工作,做好个人卫生");
}
}
}
}
//定义观众类
class Watcher extends Thread
{
TV tv;
public Watcher(TV tv)
{
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//定义电视类
class TV
{
String program;//节目名
boolean flag = true;//标志位,初始值为真
public synchronized void play(String program)
{//定义演员表演方法
if (!flag)
{//当标志位为false时等待观众观看
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//flag为真,表演节目
this.program = program;
System.out.println("演员表演的节目为:"+program);
//通知观众观看
this.notifyAll();
this.flag = !flag;
}
public synchronized void watch()
{
if (flag)
{//flag为真,观众等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//flag为假,观看节目
System.out.println("观众观看了:"+program);
this.notifyAll();//通知演员表演节目
this.flag = !flag;
}
}
9、线程池
1、背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
2、思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。
3:好处:
1)、提高响应速度(减少了创建新线程的时间)
2)、降低资源消耗(重复利用线程池中线程,不需要每次都创建新的)
3)、便于线程管理
package 多线程;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);//参数10为线程池大小
//执行线程
service.execute(new MyPool());
service.execute(new MyPool());
service.execute(new MyPool());
service.execute(new MyPool());
//关闭线程池
service.shutdown();
}
}
class MyPool implements Runnable
{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ly2tV3yF-1622943006606)(C:\Users\86131\AppData\Roaming\Typora\typora-user-images\image-20210606091437074.png)]
ram = program;
System.out.println(“演员表演的节目为:”+program);
//通知观众观看
this.notifyAll();
this.flag = !flag;
}
public synchronized void watch()
{
if (flag)
{//flag为真,观众等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//flag为假,观看节目
System.out.println("观众观看了:"+program);
this.notifyAll();//通知演员表演节目
this.flag = !flag;
}
9、线程池
1、背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
2、思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。
3:好处:
1)、提高响应速度(减少了创建新线程的时间)
2)、降低资源消耗(重复利用线程池中线程,不需要每次都创建新的)
3)、便于线程管理
package 多线程;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);//参数10为线程池大小
//执行线程
service.execute(new MyPool());
service.execute(new MyPool());
service.execute(new MyPool());
service.execute(new MyPool());
//关闭线程池
service.shutdown();
}
}
class MyPool implements Runnable
{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
[外链图片转存中…(img-ly2tV3yF-1622943006606)]