多线程简介
- 线程就是独立的执行路径。
- 程序运行前是静态,运行后启动一个进程,是系统资源分配的单位。
在程序运行时,即使没有自己创建线程,后台也会有多个线程,如main,gc线程。 - main()称为主线程,是系统的入口,用于执行整个程序。
- 在一个进程中,如果开辟了多个线程,线程的调度由调度器CPU安排,先后顺序不能人为干预。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。
- 线程会带来额外的开销,如cpu调度时间,并发控制开销。
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
线程创建(Thread,Runnable,Callable)
Thread
- 调用run()方法时代码在前,调用start()时,开启多线程,共同进行。
- 注意:线程开启后不一定立即执行,由cpu调度执行。
//创建线程方式 --- 1.继承Thread类,重写run()方法,调用start开启线程
public class Test01 extends Thread{
@Override
public void run() {
//重写run方法
for (int i = 0; i < 20; i++) {
System.out.println("run方法执行"+ i +"次");
}
}
public static void main(String[] args) {
Test01 test01 = new Test01();
// test01.run();
test01.start();
//main主线程
for (int i = 0; i < 2000; i++) {
System.out.println("这是主线程"+ i + "次");
}
}
}
- 实现多线程下同时下载图片
//实现多线程同步下载图片
public class Test02 extends Thread{
public String url;
public String FileName;
public Test02 (String url,String FileName){
this.url = url;
this.FileName = FileName;
}
//下载图片的执行体
@Override
public void run() {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.pictureDownLoad(url,FileName);
}
public static void main(String[] args) {
Test02 t1 = new Test02("https://i-blog.csdnimg.cn/blog_migrate/e6d1a0ac81a3a296cd4588638a758ed1.png","1.jpg");
Test02 t2 = new Test02("https://i-blog.csdnimg.cn/blog_migrate/c5490b1df69ab634aa69f3f611f1a87c.png","2.jpg");
t1.start();
t2.start();
}
}
//实用工具类 commons-io
class WebDownLoader{
public void pictureDownLoad(String url,String FileName){
try {
FileUtils.copyURLToFile(new URL(url),new File(FileName));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downLoad方法出错");
}
}
}
实现Runnable接口
代码实现
public class Test03 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run方法线程执行中============");
}
}
public static void main(String[] args) {
//创建Runnable接口的实现类
Test03 test03 = new Test03();
//创建线程对象,通过线程对象来开启线程,代理
Thread thread = new Thread(test03);
//启动线程
thread.start();
for (int i = 0; i < 2000; i++) {
System.out.println("maim线程执行中===========");
}
}
}
多线程下载网图=====使用Runnable
//实现多线程同步下载图片
public class Test02 implements Runnable{
public String url;
public String FileName;
public Test02 (String url,String FileName){
this.url = url;
this.FileName = FileName;
}
@Override
public void run() {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.pictureDownLoad(url,FileName);
}
public static void main(String[] args) {
Test02 t1 = new Test02("https://i-blog.csdnimg.cn/blog_migrate/e6d1a0ac81a3a296cd4588638a758ed1.png","1.jpg");
Test02 t2 = new Test02("https://i-blog.csdnimg.cn/blog_migrate/c5490b1df69ab634aa69f3f611f1a87c.png","2.jpg");
new Thread(t1).start();
new Thread(t2).start();
}
}
//
class WebDownLoader{
public void pictureDownLoad(String url,String FileName){
try {
FileUtils.copyURLToFile(new URL(url),new File(FileName));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downLoad方法出错");
}
}
}
Thread和Runnable对比
初识并发
//买火车票的例子
//多个线程操作同一个对象,线程不安全,数据紊乱
public class Asus 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) {
Asus asus = new Asus();
new Thread(asus,"线程1").start();
new Thread(asus,"线程2").start();
new Thread(asus,"线程3").start();
}
}
- 龟兔赛跑
public class Race implements Runnable {
private String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (Thread.currentThread().getName().equals("兔子") && i % 10 ==0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Boolean flag = gameOver(i);
if (flag){
break;
}
System.out.println(Thread.currentThread().getName() +"跑了====>"+i +"步");
}
}
public Boolean gameOver(int i){
if (winner != null){
return true;
}
if (i>=100){
winner = Thread.currentThread().getName();
System.out.println("winner is " + winner);
return true;
}else {
return false;
}
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
实现Callable接口
public class Call implements Callable<Boolean> {
public String url;
public String FileName;
public Call (String url,String FileName){
this.url = url;
this.FileName = FileName;
}
@Override
public Boolean call() {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.pictureDownLoad(url,FileName);
System.out.println("下载了文件: " + FileName);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Call t1 = new Call("https://i-blog.csdnimg.cn/blog_migrate/e6d1a0ac81a3a296cd4588638a758ed1.png","1.jpg");
Call t2 = new Call("https://i-blog.csdnimg.cn/blog_migrate/c5490b1df69ab634aa69f3f611f1a87c.png","2.jpg");
//创建执行服务
ExecutorService executorService = Executors.newFixedThreadPool(2);
//提交执行
Future<Boolean> submit1 = executorService.submit(t1);
Future<Boolean> submit2 = executorService.submit(t2);
//获取结果
Boolean r1 = submit1.get();
Boolean r2 = submit2.get();
//关闭服务
System.out.println(r1);
System.out.println(r2);
executorService.shutdown();
}
}
class WebDownLoader{
public void pictureDownLoad(String url,String FileName){
try {
FileUtils.copyURLToFile(new URL(url),new File(FileName));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downLoad方法出错");
}
}
}
静态代理
- 真实对象和代理对象要实现同一个接口
- 代理对象要代理真实角色
婚庆公司代理结婚。。
public class StaticProxy {
public static void main(String[] args) {
MarryCompany marryCompany = new MarryCompany(new People());
marryCompany.HappyMarry();
}
}
class People implements Marry{
@Override
public void HappyMarry() {
System.out.println("大笨蛋要结婚了!!");
}
}
class MarryCompany implements Marry{
private Marry target;
public MarryCompany(Marry target){
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void after() {
System.out.println("婚礼之后========");
}
private void before() {
System.out.println("婚礼之前========");
}
}
interface Marry {
void HappyMarry();
}
People people = new People();
类比:以下均为静态代理
new MarryCompany(people()).HappyMarry();
new Thread( () -> (System.out.println(“这是一个实现Runnable接口的”);).start();
线程状态
创建,就绪,阻塞,运行,死亡。
-线程方法
- 线程停止:stop
- 建议线程正常停止—>利用次数,不建议死循环
- 建议使用标志位(flag)—>设置一个标志位
- 不要使用stop、destroy等过时或JDK不推荐使用的方法。
public class Stop implements Runnable{
private Boolean flag = true;
@Override
public void run() {
int i =0;
while (flag){
System.out.println("线程运行中=========" + i++);
}
}
public void flagChange(){
this.flag = false;
}
public static void main(String[] args) {
Stop stop = new Stop();
new Thread(stop).start();
for (int i = 0; i < 10000; i++) {
System.out.println("main线程执行中======"+ i);
if (i==5000){
stop.flagChange();
System.out.println("线程该停止了!");
}
}
}
}
- 线程休眠 sleep
代码参考初识并发 火车票
- 模拟网络延时:方法问题的发生性,否则示例中票会被单一线程全部拿走。
- 模拟倒计时
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
tenDown();
}
public static void tenDown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num<0){
break;
}
}
}
}
- 打印系统当前时间、间隔
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
Date time = new Date(System.currentTimeMillis());
while (true){
Thread.sleep(500);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(time));
time = new Date(System.currentTimeMillis());
}
}
}
- 线程礼让:yield
- 线程执行暂停
- 礼让不一定成功,cpu调度不一定。
- 线程插队:join
- 待此线程结束后,再执行其他线程,其他线程暂时阻塞。
- 理解为插队
public class TestJoin implements Runnable{
@Override
public void run() {
System.out.println("thread=====");
}
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==50){
thread.join();
}
System.out.println("main====="+i);
}
}
}
- 线程状态观测
Thread.State
- 线程优先级
优先级不一定高的就会先跑,只是提供一个参考。
public class TestPrior{
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName()+"=====>"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
//设置线程优先级
t1.setPriority(3);
t1.start();
t2.setPriority(8);
t2.start();
t3.setPriority(10);
t3.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"=====>"+Thread.currentThread().getPriority());
}
}
- 守护线程
- 线程分为守护线程和用户线程
- 虚拟机必须等待用户线程执行完毕。(main线程结束即程序运行终止)
- 虚拟机不必等待守护线程执行完毕。(gc垃圾回收,后台记录操作日志,监控内存)
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
//设置为main守护线程,默认false 关闭
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 < 100; i++) {
System.out.println("你一直好好活着====");
}
}
}
- 线程同步
- 并发:同个对象被多个线程同时操作。
线程同步其实是一种等待机制,多个线程同时访问此对象的线程进入这个对象的等待池形成队列,等待前一个线程使用完毕,下一个线程再使用。
解决安全性:队列+锁。。每个对象都有一把锁。
每个线程都在自己的工作内存交互,内存控制不当会造成数据不一致。
线程不安全举例
1.并发买票
public class UnSafe {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"Thread1").start();
new Thread(buyTicket,"Thread2").start();
new Thread(buyTicket,"Thread3").start();
}
}
class BuyTicket implements Runnable{
int ticketNum = 10;
//判断票是否售空
Boolean flag = true;
@Override
public void run() {
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//买票
public void buy() throws InterruptedException {
//判断是否有票
if (ticketNum <=0){
flag = false;
return;
}
//模拟延时,放大问题的发生性
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + "买到了票"+ ticketNum--);
}
}
2.例子:线程不安全的集合
public class UnSafe {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
list.add(Thread.currentThread().getName());
}
}).start();
}
System.out.println(list.size());
}
}
应为10000,多个线程同时写入,次数会被覆盖,所以空间会减小
线程同步方法
//买票
//synchronized 修饰符 锁住了此方法,线程需要拿到锁之后才可通行
public synchronized void buy() throws InterruptedException {
//判断是否有票
if (ticketNum <=0){
flag = false;
return;
}
//模拟延时,放大问题的发生性
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + "买到了票"+ ticketNum--);
}
//集合
for (int i = 0; i < 2000; i++) {
new Thread(() -> {
//同步代码块 注意:不加主线程休眠 仍然锁不住
//监视的对象 是需要增删改 的对象!!
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(1000);
JUC中线程安全类型的集合
//和Runnable接口同属于 java.util.concurrent 下
public class UnSafe {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
for (int i = 0; i < 2000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}
).start();
}
System.out.println(Thread.currentThread().getName());
Thread.sleep(1000);
System.out.println(list.size());
}
}
- 死锁
指多个线程各自占有一些共享资源,并且互相等待其他线程占用的资源,从而导致两个或多个线程互相等待的对方释放资源而都停止执行的情况
//多个线程互相抱着对方的资源 而造成死锁 僵持
public class DeadLock {
public static void main(String[] args) {
MakeUp g1 = new MakeUp(0,"灰姑凉");
MakeUp g2 = new MakeUp(1,"白雪公主");
g1.start();
g2.start();
}
}
class Lipstick{
}
class Mirror{
}
class MakeUp extends Thread{
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;
String girlName;
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
MakeUp(int choice,String girlName){
this.choice = choice;
this.girlName = girlName;
}
private void makeup() throws InterruptedException {
if (choice == 0){
synchronized (lipstick){
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(1000);
//这种情况下会出现死锁情况,改善就是 把第二个synchronized 代码块拿出来
synchronized (mirror){
System.out.println(this.girlName + "获得镜子的锁");
}
}
}else {
synchronized (mirror){
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(2000);
//这种情况下会出现死锁情况,改善就是 把第二个synchronized 代码块拿出来
synchronized (lipstick){
System.out.println(this.girlName + "获得口红的锁");
}
}
}
}
}
解决:
互斥条件:一个资源每次只能被一个进程使用
请求与保持条件:一个进程因请求资源而阻塞时对已获得的资源保持不放
不剥夺条件:进程已获得的资源,在未使用完时,不能清醒剥夺。
- Lock锁
public class TestLock {
public static void main(String[] args) {
Station station = new Station();
new Thread(station).start();
new Thread(station).start();
new Thread(station).start();
new Thread(station).start();
}
}
class Station implements Runnable {
//创建 ReentrantLock 锁对象
ReentrantLock lock = new ReentrantLock();
int ticketNums = 10;
@Override
public void run() {
while (true) {
try {
//开启锁
lock.lock();
if (ticketNums > 0) {
System.out.println("取到了票" + ticketNums--);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
} finally {
//关闭锁
lock.unlock();
}
}
}
}
- 线程协作
生产者消费者模式,一个问题不是模式
这是一个线程同步问题,生产者和消费者公用同一个资源,并且生产者和消费者之间互为条件,互相依赖。
在此问题中 仅有synchronized是不够的,它能阻止并发更新同一个共享资源,实现了同步,但是不能用来实现不同线程之间的消息传递(通信)。
解决方法一:
生产消费鸡问题:
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 synContainer;
public Productor(SynContainer synContainer) {
this.synContainer = synContainer;
}
//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了" + i + "只鸡");
try {
synContainer.push(new Chicken(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer extends Thread {
SynContainer synContainer;
public Consumer(SynContainer synContainer) {
this.synContainer = synContainer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
System.out.println("消费了--》" + synContainer.pop().id + "只鸡");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Chicken extends Thread {
int id;
public Chicken(int id) {
this.id = id;
}
}
class SynContainer {
//需要一个容器大小
Chicken[] chickens = new Chicken[10];
int count = 0;
//生产者放入鸡
public synchronized void push(Chicken chicken) throws InterruptedException {
//如果容器满了,要等待消费者消费
if (count == chickens.length) {
//通知消费者消费
this.wait();
}
//如果没满,要丢入产品
chickens[count] = chicken;
count++;
this.notifyAll();
}
public synchronized Chicken pop() throws InterruptedException {
//判断能否消费
if (count == 0) {
//通知生产者生产,消费者等待
this.wait();
}
//如果可以消费,消费
count--;
Chicken chicken = chickens[count];
//通知生产者生产
this.notifyAll();
return chicken;
}
}
解决方法二:
public class TestPC2 {
public static void main(String[] args) {
Tv tv = new Tv();
new Actor(tv).start();
new Watcher(tv).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) {
try {
this.tv.play ("----快乐大本营播放中----");
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
try {
this.tv.play("-----广告正在播放----");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//消费者-->观众
class Watcher extends Thread {
Tv tv;
public Watcher(Tv tv) {
this.tv = tv;
}
@Override
public void run() {
//有生产就要有消费
for (int i = 0; i < 20; i++) {
try {
tv.watch();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Tv {
//演员表演,观众等待 T
//观众观看,演员等待 F
String voice;
Boolean flag = true;
//表演
public synchronized void play(String voice) throws InterruptedException {
if (!flag) {
this.wait();
}
System.out.println("演员表演了:" + voice);
this.voice = voice;
//通知观众观看
this.notifyAll();//通知唤醒
this.flag = !this.flag;
}
//观看
public synchronized void watch() throws InterruptedException {
if (flag) {
this.wait();
}
System.out.println("观众观看了:" + voice);
//通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
- 使用线程池
参照Callable接口的实现
public class TestPool {
public static void main(String[] args) {
//1.创建服务,创建线程池
//newFixedThreadPool 参数为:线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//2.关闭连接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}