// 创建新线程的方法: 1、继承extends Thread 2、重写 run() 3、实例化后start()开启
public class TestThread extends Thread{
// 重写 run()
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我是新线程------"+i);
}
}
// 主线程
public static void main(String[] args) {
// 创建线程对象
TestThread testThread1 = new TestThread();
testThread1.start();
for (int i = 0; i < 1000; i++) {
System.out.println("我是主线程------"+i);
}
}
}
/**
* 主线程与新线程执行顺序由cpu去调度。
*/
/**
* @Description 加入common.io-2.11.0.jar包,然后开启多线程下载网络图片
*/
public class TestThread1 extends Thread {
private String url;
private String name;
public TestThread1(String url, String name) {
this.url = url;
this.name = name;
}
public void run() {
webDownLoader Down = new webDownLoader();
Down.downLoader(this.url, this.name);
System.out.println("下载成功"+ this.name);
}
public static void main(String[] args) {
TestThread1 t1 = new TestThread1("https://www.kuangstudy.com/assert/images/coursebg.jpg", "1.jpg");
TestThread1 t2 = new TestThread1("https://www.kuangstudy.com/assert/images/xiaok.png", "2.jpg");
TestThread1 t3 = new TestThread1("https://www.kuangstudy.com/assert/images/index_topleft_logo_black.png", "3.jpg");
t1.start();
t2.start();
t3.start();
}
}
class webDownLoader {
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");
}
}
}
我的理解就是,继承Thread类的化,每次开启新线程,则需要 new TestThread1()
创建一个新的对象,开启一个就多一个新对象。而通过 implements runnable
接口开启新线程,只需要将一个对象放入 new Thread(实现了runnable的类的实例)
就可以了,开启多个新线程,公用一个对象,自然是更好的啦!
初识并发问题: 多个线程同时操作一个资源,会出现资源紊乱,不安全!例子:比如多线程抢火车票,可能不同线程抢到同一张票,出现超卖的问题!
使用callable 接口实现与使用runnable接口实现多线程对比:
实际上new Thread(实现了runnable的类的实例)
就是一种静态代理模式, Thread类和你创建的对象都实现了 Runnable
接口, 并且 new Thread(xxx)
帮助你去实现更多的事情!
也就是说如果是函数式接口(只包含一个抽象方法),我们就可以使用lamda
表达式,来简化书写
// 一步一步简化的过程分析, 从外部类->静态内部类->局部内部类->匿名内部类, 需要有一个接口或者父类->lambda
public class Test01 {
//静态内部类
static class Test02 implements Runnable{
@Override
public void run() {
System.out.println("你好呀lambda!");
}
}
public static void main(String[] args) {
Test02 test02 = new Test02();
new Thread(test02).start();
//局部内部类
class Test03 implements Runnable{
@Override
public void run() {
System.out.println("你好呀lambda2!");
}
}
Test03 test03 = new Test03();
new Thread(test03).start();
//匿名内部类, 需要有一个接口或者父类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("你好呀lambda3!");
}
}).start();
//lambda jdk.8新增方式
new Thread(()->{
System.out.println("你好呀lambda4!");
}).start();
}
}
public class TestSleep2 {
public static void main(String[] args) throws InterruptedException {
TestSleep2 testSleep2 = new TestSleep2();
//获取系统时间
Date startTime = new Date(System.currentTimeMillis());
while (true) {
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
// 线程的休眠方法
Thread.sleep(1000);
startTime = new Date(System.currentTimeMillis());
}
}
//模拟倒计时方法
private void tenDown() throws InterruptedException {
int num = 10;
for (int i = 10; i > 0; i--) {
// 线程的休眠方法
Thread.sleep(1000);
System.out.println("倒计时:"+i);
}
}
}
public class TestJoin implements Runnable{
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 100; i++) {
if (i==80){
//强制执行,而且会阻塞主线程,等到thread线程跑完了,主线程才继续执行
thread.join();
}
System.out.println("我是主线程:"+i);
}
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我是要插队的线程:"+i);
}
}
}
线程可以通过 thread.getState()
来获得当前线程的状态
状态分类:
//测试线程优先级
public class TestPriority {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread thread1 = new Thread(myPriority);
Thread thread2 = new Thread(myPriority);
Thread thread3 = new Thread(myPriority);
Thread thread4 = new Thread(myPriority);
Thread thread5 = new Thread(myPriority);
thread1.setPriority(1);
thread1.start();
thread2.setPriority(4);
thread2.start();
thread3.setPriority(8);
thread3.start();
thread4.setPriority(9);
thread4.start();
thread5.setPriority(10);
thread5.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
优先级只是给cpu一个参考,实际还是看它心情!
//测试守护线程
//用户线程: 虚拟机要等待用户线程执行完毕
//守护线程: 虚拟机 不用等待 守护线程 执行完毕
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
Thread thread = new Thread(god);
thread.setDaemon(true); //设置线程为守护线程,默认为false
thread.start();
//用户线程
new Thread(()->{
for (int i = 0; i < 30; i++) {
System.out.println("你开心的在这个世界上活着"+i);
}
System.out.println("=======Goodbye , World!");
}).start();
}
}
//守护线程
class God implements Runnable{
@Override
public void run() {
for (;true;){
System.out.println("上帝保佑着你");
}
}
}
三大不安全的例子
我认为不安全造成的原因都是因为,同一时间,多个线程操作同一对象,导致发生对象数据变化发生错误,不是正常的改变,因为同时操作的缘故。
就好比只有一张票,三个人同时抢,对于抢的一瞬间,三个人都能看到那张票,也因此都抢到了,而此时系统被抢一张就减去一,导致系统最后存放的数据是-2,也就是前面所说的导致对象数据变化发生错误
//线程同步问题一 :线程不安全,买票问题
public class UnsafeBuyTicket implements Runnable {
//票数
private int ticketNums = 10;
//标志位
private boolean flag = true;
@Override
public void run() {
//买票
while (flag) {
buyTicket();
}
}
public void buyTicket() {
if (ticketNums <= 0) {
flag = false;
return;
}
//模拟网络延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticketNums-- + "张票");
}
public static void main(String[] args) {
UnsafeBuyTicket station = new UnsafeBuyTicket();
new Thread(station,"苦逼的我").start();
new Thread(station,"牛逼的你们").start();
new Thread(station,"可恶的黄牛党").start();
}
}
//银行(存钱:存了多少,取钱:去了多少) , 两个人 , 账户
//并发问题,线程不安全
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100,"招商卡");
Bank you = new Bank("痛苦的你",account,50);
Bank wife = new Bank("开心的媳妇",account,100);
you.start();
wife.start();
}
}
//账户
class Account{
int money;//余额
String name; //卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行
class Bank extends Thread{
//存钱:存了多少,取钱:取了多少
Account account; //账户
int drawingMoney; //取了多少钱
int nowMoney; //手里有多少钱
public Bank(String name,Account account,int drawingMoney){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
//判断能否取钱
if (account.money-drawingMoney<0){
return;
}
//为了放大问题发生性,我们加个延时.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//余额 = 余额 - 你去走的钱
account.money = account.money - drawingMoney;
//你的钱 = 你的钱 + 你取的钱
nowMoney = drawingMoney + nowMoney;
System.out.println(this.account.name+"账户余额:"+account.money);
System.out.println(this.getName()+"手里的钱:"+nowMoney);
}
}
package com.kuang.syn;
import java.util.ArrayList;
import java.util.List;
//线程不安全问题3,集合操作
//思考?怎么让这些问题变安全.
public class UnSafeList {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 20000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
for (int i = 5;i>0;i--){
Thread.sleep(1000);
System.out.println("倒计时"+i);
}
System.out.println(list.size());
}
}
// 第一个案例只需要锁住买票的方法就可以了,实际上就是锁住了票这个共享资源
public synchronized void buyTicket() {
if (ticketNums <= 0) {
flag = false;
return;
}
//模拟网络延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticketNums-- + "张票");
}
第二个案例,给 public void run()
给这个方法上锁是不行的,锁住的是银行这个对象,而实际上共享的资源(会被线程修改的资源)是放在账户这个对象当中。此时就需要用到同步块的概念!
每个对象都有自己的一把索,利用 synchronized
修饰关键字,我们可以让当前方法或者块所在的线程获得这把锁,那么其它线程,想去操作个他们的这个方法或者块被禁止,只有等到这把锁被释放,才行!
public void run() {
drwaing();
}
//synchronized本身锁的是this.就是这个对象本身
public void drwaing(){
//提高性能的代码
if (account.money<=0){
return;
}
//如何判断锁的对象
// 谁需要实现增删改就去锁定他
synchronized (account){ // ---------这里就是关键----------
//判断能否取钱
if (account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"活该,没取到钱");
return;
}
//为了放大问题发生性,我们加个延时.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//余额 = 余额 - 你去走的钱
account.money = account.money - drawingMoney;
//你的钱 = 你的钱 + 你取的钱
nowMoney = drawingMoney + nowMoney;
System.out.println(this.account.name+"账户余额:"+account.money);
System.out.println(this.getName()+"手里的钱:"+nowMoney);
}
}
同步方法: 也就是加在方法上的 synchronized
是锁住当前方法所在的对象,也就是this
同步块: synchronized(account){...}
是锁住传入的参数所对应的这个对象
我的理解就是: 锁的概念在于,同时的情况下,保证只有一个线程在操作需要修改的共享资源!!!
package com.kuang.syn;
import java.util.concurrent.CopyOnWriteArrayList;
public class SafeJUCList {
public static void main(String[] args) throws InterruptedException {
//保证线程安全的list , ArrayList
CopyOnWriteArrayList list = new CopyOnWriteArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
// 这里就是属于用synchronized方法来锁住要修改的对象list,来保证安全
new Thread(()->{
synchronized(list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
for (int i = 5;i>0;i--){
Thread.sleep(1000);
System.out.println("倒计时"+i);
}
System.out.println(list.size());
}
}
我的理解:多线程都想要对方拥有的资源(而这个资源又是独一份的,也就是用static定义的,只执行一次)的锁,形成僵持,也就是死锁! 放在代码中就是
// 也就是同步块想要拥有两个以上对象的锁
// 线程1
synchronized(a) {
....
synchronized(b) {
...
}
}
// 线程2
synchronized(b) {
....
synchronized(a) {
...
}
}
// 解决方案:将里面的抽离出来,那么就会a,b对象的锁都会用完就释放,也就不会形成死锁了
// 线程1
synchronized(a) {
....
}
synchronized(b) {
...
}
// 线程2
synchronized(b) {
....
}
synchronized(a) {
...
}
例子:
package com.kuang.gaoji;
//死锁问题
//两个线程抱着自己的锁 , 然后想要对方的锁 .
// 于是产生一个问题 ---> 死锁
public class DeadLocked {
public static void main(String[] args) {
Makeup g1 = new Makeup(0,"白雪公主");
Makeup g2 = new Makeup(1,"灰姑凉");
new Thread(g1).start();
new Thread(g2).start();
}
}
//化妆
class Makeup implements Runnable{
//选择
int choice;
//谁进来了
String girlName;
//两个对象,而且是独一份的资源, 因为static只执行一次
static LipStick lipStick = new LipStick();
static Mirror mirror = new Mirror();
public Makeup(int choice,String girlName){
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
//化妆
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妆的方法
public void makeup() throws InterruptedException {
if (choice==0){ //先拿口红,再拿镜子
synchronized (lipStick){
System.out.println("拿到口红");
Thread.sleep(1000);
//等待拿镜子的人释放锁
synchronized (mirror){
System.out.println("拿到镜子");
}
}
}else { //先拿镜子 , 再拿口红
synchronized (mirror){
System.out.println("拿到镜子");
Thread.sleep(2000);
//等待拿口红的人释放锁
synchronized (lipStick){
System.out.println("拿到口红");
}
}
}
}
}
//口红
class LipStick{
}
//镜子
class Mirror{
}
package com.kuang.gaoji;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
HelloWorld helloWorld = new HelloWorld();
new Thread(helloWorld).start();
new Thread(helloWorld).start();
}
}
class HelloWorld implements Runnable{
int ticketNums = 100;
//可重入锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock(); //加锁
//判断是否有票
if (ticketNums>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
}else {
break;
}
} finally {
lock.unlock();//解锁
}
}
}
}
管程法
/*
生产者只管生产
消费者只管消费
鸡: 实体类
容器 :
容器添加数据.
要判断容器是否满 , 满了等待消费者消费
没有满,通知生产者生产
容器减少数据
判断还有没有数据, 没有数据的话 . 等待生产者生产
消费完毕 , 通知生产者生产
*/
import java.sql.SQLOutput;
//测试生产者和消费者问题
public class TestPC {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
new Productor(synContainer).start();
new Consumer(synContainer).start();
}
}
//生产者
class Productor extends Thread{
//需要向容器中加入产品
SynContainer container;
public Productor(SynContainer container){
this.container = container;
}
@Override
public void run() {
for (int i = 1; i < 100; i++) {
//生产者添加产品
container.push(new Chicken(i));
System.out.println("生产者生产了"+i+"鸡");
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container = container;
}
@Override
public void run() {
for (int i = 1; i < 100; i++) {
//消费者拿走产品
Chicken chicken = container.pop();
System.out.println("消费者消费了"+chicken.id+"鸡");
}
}
}
//缓冲区-->容器
class SynContainer{
//容器
Chicken[] chickens = new Chicken[10];
//容器的计数器
int num = 0;
//生产者放入产品
public synchronized void push(Chicken chicken) {
//假如容易已经满了,就不用放,等待消费者消费
if (num>=chickens.length){
//等待消费者消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//假如容器没有满 , 通知生产生成
System.out.println("num,,,,,"+num);
chickens[num] = chicken;
System.out.println("数组有多少个元素"+num);
num++;
//通知消费者消费
this.notifyAll();
}
//消费者拿走产品
public synchronized Chicken pop(){
//假如容器空的,等待
if (num<=0){
//等待生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
Chicken chicken = chickens[num];
//通知生产者生产
this.notifyAll();
return chicken;
}
}
//产品->鸡
class Chicken {
int id;
public Chicken(int id) {
this.id = id;
}
}
信号灯法(也叫标志位法)
package com.kuang.gaoji;
//生产者消费2
//生产者--->演员
//消费者--->观众
//产品:信号灯--->电视----->声音
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者
class Player extends Thread{
TV tv;
public Player(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2==0){
this.tv.play("节目:快乐大本营播放中");
System.out.println();
}else {
this.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{
//演员说话 , 观众等待
//观众观看 , 演员等待
boolean flag = true;
//说话
String voice;
//表演
public synchronized void play(String voice){
//演员等待
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("表演了"+voice);
this.voice = voice;
//让观众观看
this.notifyAll();
this.flag = !this.flag;
}
//观看
public synchronized void watch(){
//观众等待
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众听到了: "+voice);
//通知演员说话
this.notifyAll();
this.flag = !this.flag;
}
}
this.wait()
会将当前线程丢入等待池中,等待被唤醒,并且立即释放对象的锁,这样别的线程可以占用该锁了
this.notifyAll()
会唤醒同一对象中所有调用 this.wait()
这个方法被丢入等待池的线程
boolean flag = true;
//说话
String voice;
//表演
public synchronized void play(String voice){
//演员等待
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("表演了"+voice);
this.voice = voice;
//让观众观看
this.notifyAll();
this.flag = !this.flag;
}
//观看
public synchronized void watch(){
//观众等待
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众听到了: "+voice);
//通知演员说话
this.notifyAll();
this.flag = !this.flag;
}
}
this.wait() 会将==当前线程==丢入==等待池==中,等待被唤醒,并且立即==释放对象的锁==,这样别的线程可以占用该锁了
this.notifyAll() 会唤醒同一对象中所有调用 this.wait()这个方法被丢入等待池的线程