-
2020-01-01 17:59:57
文章目录
1、要求线程a执行完才开始线程b, 线程b执行完才开始线程
package com.example.javatest.theardTest.MultiThreadAlgorithm; /** * 要求线程a执行完才开始线程b, 线程b执行完才开始线程 * * join()解释:https://blog.csdn.net/qq_18505715/article/details/79795728 * * wait() 和 notify() 解释:https://blog.csdn.net/chaozhi_guo/article/details/50249177 * * join()的作用:主要作用是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。 * * public void joinDemo(){ * //.... * Thread t=new Thread(payService); * t.start(); * //.... * //其他业务逻辑处理,不需要确定t线程是否执行完 * insertData(); * //后续的处理,需要依赖t线程的执行结果,可以在这里调用join方法等待t线程执行结束 * t.join(); * } * */ public class T1T2T3 { public static class PrintThread extends Thread{ PrintThread(String name){ super(name); } @Override public void run() { for(int i = 0; i < 100; i++){ System.out.println(getName() + " : " + i); } } } public static void main(String[] args){ PrintThread t1 = new PrintThread("a"); PrintThread t2 = new PrintThread("b"); PrintThread t3 = new PrintThread("c"); try { t1.start(); t1.join(); t2.start(); t2.join(); t3.start(); t3.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 我对于join方法的理解: * * join() 的源码: * public final void join(long millis) throws InterruptedException { * synchronized(lock) { * ... * * while (isAlive()) { * lock.wait(0); * } * ... * } * } * * 其实就是main()线程调用join()后,synchronized(lock)语句块,获得lock的锁, * * 然后判断如果t1线程isAlive(), 就一直lock.wait(), 让自己(main()线程)阻塞住, * * 直到t1线程 !isAlive 后才不wait, 等待着被notify(), 然后t1 die后会调用lock.notifyAll()。 * * * 注意:这里lock.wait(0)虽然在t1.join()内,但是join()内的代码不是运行在t1线程中,而是运行在main()线程中, * t1线程中运行的是其run()方法内的代码。 * */
2、两个线程轮流打印数字,一直到100
package com.example.javatest.theardTest.MultiThreadAlgorithm; /** * 两个线程轮流打印数字,一直到100 * * Java的wait()、notify()学习: * https://blog.csdn.net/boling_cavalry/article/details/77995069 */ public class TakeTurnsPrint { public static class TakeTurns { private static boolean flag = true; private static int count = 0; public synchronized void print1() { for (int i = 1; i <= 50; i++) { while (!flag) { try { System.out.println("print1: wait before"); wait(); System.out.println("print1: wait after"); } catch (InterruptedException e) { } } System.out.println("print1: " + ++count); flag = !flag; notifyAll(); } } public synchronized void print2() { for (int i = 1; i <= 50; i++) { while (flag) { try { System.out.println("print2: wait before"); wait(); System.out.println("print2: wait after"); } catch (InterruptedException e) { } } System.out.println("print2: " + ++count); flag = !flag; notifyAll(); } } } public static void main(String[] args){ TakeTurns takeTurns = new TakeTurns(); new Thread(new Runnable() { @Override public void run() { takeTurns.print1(); } }).start(); new Thread(new Runnable() { @Override public void run() { takeTurns.print2(); } }).start(); } }
3、写两个线程,一个线程打印1~ 52,另一个线程打印A~Z,打印顺序是12A34B…5152Z
package com.example.javatest.theardTest.MultiThreadAlgorithm; /** * 两线程,一个打印数字从1到52,另一个打印字母从A到Z,输出:12A34B56C...5152Z */ public class TakeTurnsPrint2 { private boolean flag; private int count; public synchronized void printNum() { for (int i = 0; i < 26; i++) { while (flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } flag = !flag; System.out.print(++count); System.out.print(++count); notify(); } } public synchronized void printLetter() { for (int i = 0; i < 26; i++) { while (!flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } flag = !flag; System.out.print((char) (65 + i)); notify(); } } public static void main(String[] args) { TakeTurnsPrint2 turnsPrint2 = new TakeTurnsPrint2(); new Thread(new Runnable() { @Override public void run() { turnsPrint2.printNum(); } }).start(); new Thread(new Runnable() { @Override public void run() { turnsPrint2.printLetter(); } }).start(); } }
4、编写一个程序,启动三个线程,三个线程的ID分别是A,B,C;,每个线程将自己的ID值在屏幕上打印5遍,打印顺序是ABCABC…
package com.example.javatest.theardTest.MultiThreadAlgorithm; /** * 编写一个程序,启动三个线程,三个线程的ID分别是A,B,C;,每个线程将自己的ID值在屏幕上打印5遍,打印顺序是ABCABC... */ public class ABCABCABC { private int flag = 0; public synchronized void printA() { for(int i = 0; i < 5; i++) { while (flag != 0) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } flag = 1; System.out.print("A"); notifyAll(); } } public synchronized void printB() { for(int i = 0; i < 5; i++) { while (flag != 1) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } flag = 2; System.out.print("B"); notifyAll(); } } public synchronized void printC() { for(int i = 0; i < 5; i++) { while (flag != 2) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } flag = 0; System.out.print("C"); notifyAll(); } } public static void main(String[] args) { ABCABCABC abcabcabc = new ABCABCABC(); new Thread(new Runnable() { @Override public void run() { abcabcabc.printA(); } }).start(); new Thread(new Runnable() { @Override public void run() { abcabcabc.printB(); } }).start(); new Thread(new Runnable() { @Override public void run() { abcabcabc.printC(); } }).start(); } }
5、编写10个线程,第一个线程从1加到10,第二个线程从11加20…第十个线程从91加到100,最后再把10个线程结果相加。
/** * 编写10个线程,第一个线程从1加到10,第二个线程从11加20…第十个线程从91加到100,最后再把10个线程结果相加 */ public class TenThreadSum { public static class SumThread extends Thread{ int forct = 0; int sum = 0; SumThread(int forct){ this.forct = forct; } @Override public void run() { for(int i = 1; i <= 10; i++){ sum += i + forct * 10; } System.out.println(getName() + " " + sum); } } public static void main(String[] args) { int result = 0; for(int i = 0; i < 10; i++){ SumThread sumThread = new SumThread(i); sumThread.start(); try { sumThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } result = result + sumThread.sum; } System.out.println("result " + result); } }
6 、三个窗口同时卖票
/** * 三个窗口同时卖票 */ /** * 票 */ class Ticket { private int count = 1; public void sale() { while (true) { synchronized (this) { if (count > 200) { System.out.println("票已经卖完啦"); break; } else { System.out.println(Thread.currentThread().getName() + "卖的第 " + count++ + " 张票"); } try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } } } /** * 售票窗口 */ class SaleWindows extends Thread { private Ticket ticket; public SaleWindows(String name, Ticket ticket) { super(name); this.ticket = ticket; } @Override public void run() { super.run(); ticket.sale(); } } public class TicketDemo { public static void main(String[] args) { Ticket ticket = new Ticket(); SaleWindows windows1 = new SaleWindows("窗口1", ticket); SaleWindows windows2 = new SaleWindows("窗口2", ticket); SaleWindows windows3 = new SaleWindows("窗口3", ticket); windows1.start(); windows2.start(); windows3.start(); } }
7、 生产者消费者
7.1 synchronized方式
public class FruitPlateDemo { private final static String LOCK = "lock"; private int count = 0; private static final int FULL = 10; public static void main(String[] args) { FruitPlateDemo fruitDemo1 = new FruitPlateDemo(); for (int i = 1; i <= 5; i++) { new Thread(fruitDemo1.new Producer(), "生产者-" + i).start(); new Thread(fruitDemo1.new Consumer(), "消费者-" + i).start(); } } class Producer implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { synchronized (LOCK) { while (count == FULL) { try { LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("生产者 " + Thread.currentThread().getName() + " 总共有 " + ++count + " 个资源"); LOCK.notifyAll(); } } } } class Consumer implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { synchronized (LOCK) { while (count == 0) { try { LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("消费者 " + Thread.currentThread().getName() + " 总共有 " + --count + " 个资源"); LOCK.notifyAll(); } } } } }
7.2 ReentrantLock方式 (可以保证顺序)
public class Demo1 { private int count = 0; private final static int FULL = 10; private Lock lock; private Condition notEmptyCondition; private Condition notFullCondition; { lock = new ReentrantLock(); notEmptyCondition = lock.newCondition(); notFullCondition = lock.newCondition(); } class Producer implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { lock.lock(); try { while(count == FULL) { try { notFullCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("生产者 " + Thread.currentThread().getName() + " 总共有 " + ++count + " 个资源"); notEmptyCondition.signal(); } finally { lock.unlock(); } } } } class Consumer implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { lock.lock(); try { while(count == 0) { try { notEmptyCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("消费者 " + Thread.currentThread().getName() + " 总共有 " + --count + " 个资源"); notFullCondition.signal(); } finally { lock.unlock(); } } } } public static void main(String[] args) { Demo1 demo1 = new Demo1(); for (int i = 1; i <= 5; i++) { new Thread(demo1.new Producer(), "生产者-" + i).start(); new Thread(demo1.new Consumer(), "消费者-" + i).start(); } } }
7.3 BlockingQueue方式
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class Demo2 { private int count = 0; private BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10); public static void main(String[] args) { Demo2 demo2 = new Demo2(); for (int i = 1; i <= 5; i++) { new Thread(demo2.new Producer(), "生产者-" + i).start(); new Thread(demo2.new Consumer(), "消费者-" + i).start(); } } class Producer implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { try { queue.put(1); System.out.println("生产者 " + Thread.currentThread().getName() + " 总共有 " + ++count + " 个资源"); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Consumer implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { try { queue.take(); System.out.println("消费者 " + Thread.currentThread().getName() + " 总共有 " + --count + " 个资源"); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
8、交替打印两个数组
package com.example.javatest.theardTest.MultiThreadAlgorithm; public class TwoArr { int[] arr1 = new int[]{1, 3, 5, 7, 9}; int[] arr2 = new int[]{2, 4, 6, 8, 10}; boolean flag; public synchronized void print1(){ for(int i= 0; i < arr1.length; i++){ while (flag){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } flag = !flag; System.out.println(arr1[i]); notifyAll(); } } public synchronized void print2(){ for(int i= 0; i < arr2.length; i++){ while (!flag){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } flag = !flag; System.out.println(arr2[i]); notifyAll(); } } public static void main(String[] args) { TwoArr twoArr = new TwoArr(); new Thread(new Runnable() { @Override public void run() { twoArr.print1(); } }).start(); new Thread(new Runnable() { @Override public void run() { twoArr.print2(); } }).start(); } }
更多相关内容 -
Java 多线程编程基础(详细)
2020-11-03 17:36:30Java多线程编程基础进程与线程多线程实现Thread类实现多线程Runnable接口实现多线程Callable接口实现多线程多线程运行状态多线程常用操作方法线程的命名和获取线程休眠线程中断线程强制执行线程让步线程优先级设定...Java多线程编程基础
1. 进程与线程
进程与线程的基本认识
进程(Process):进程是程序的一次动态执行过程,它经历了从代码加载、执行、到执行完毕的一个完整过程;同时也是并发执行的程序在执行过程中分配和管理资源的基本单位,竞争计算机系统资源的基本单位。
线程(Thread):线程可以理解为进程中的执行的一段程序片段,是进程的一个执行单元,是进程内可调度实体,是比进程更小的独立运行的基本单位,线程也被称为轻量级进程。
为什么会有线程
每个进程都有自己的地址空间,即进程空间,在网络或多用户换机下,一个服务器通常需要接收大量不确定数量用户的并发请求,为每一个请求都创建一个进程显然行不通,即使多进程可以提高硬件资源的利用率,但是进程的启动和销毁需要消耗大量的系统性能,从而导致程序的执行性能下降。所以为了进一步提升并发操作的处理能力,在进程的基础上又划分了多线程概念。
以B站看视频为例:
打开B站看视频可以理解为实现了一个“进程”,而在看的时候,会同时听到声音,看到图片,还有可以发弹幕等……这些都是多线程实现。
简而言之:一个程序至少一个进程,一个进程至少一个线程。
2. 多线程实现
在java中,如果要实现多线程,就必须依靠线程主体类,而
java.lang.Thread
是java中负责多线程操作类,只需继承Thread类,就能成为线程主体类,为满足一些特殊要求,也可以通过实现Runnable接口或者Callable接口来完成定义。具体方式如下:
1.继承Thread类,重写run方法(无返回值)
2.实现Runnable接口,重写run方法(无返回值)
3.实现Callable接口,重写call方法(有返回值且可以抛出异常)
2.1 Thread类实现多线程
通过继承Thread类,并重写父类的run()方法实现
public void run()
定义线程类:
class MyThread extends Thread{ private String name; public MyThread(String name) { this.name = name; } @Override public void run() { for(int i = 0 ; i < 50 ; i++) { System.out.println(this.name + "正在工作中……" + i); } } }
多线程启动:
public class testThread { public static void main(String[] args) { // 实例化线程对象 MyThread mt1 = new MyThread("线程一"); MyThread mt2 = new MyThread("线程二"); MyThread mt3 = new MyThread("线程三"); // 启动实例线程对象 mt1.start(); mt2.start(); mt3.start(); } }
运行情况:
以上结果可以发现,它并不是按照顺序执行,而是以一种随机交替方式执行的,每次的结果都可能不一样。通过以上代码可以知道,要想实现多线程,就要依靠Thread类的start()方法执行,线程启动后会默认调用run()方法。
2.2 Runnable接口实现多线程
使用Thread类的确可以实现多线程,但是也容易发现它的缺陷:面向对象的单继承局限,因此才采用Runnable接口来实现多线程。
该接口的定义如下(以下并不需要在代码中实现):
@FunctionalInterface public interface Runnable{ public void run(); }
定义线程类:
public class MyThread implements Runnable{ private String name; public MyThread(String name) { this.name = name; } @Override public void run() { for(int i = 0 ; i<50 ;i++) { System.out.println(this.name + " 正在执行中……" + i); } } }
多线程启动:
public class testThread { public static void main(String[] args) { // 实例化继承Runnable接口的MyThread类 Runnable mt1 = new MyThread("线程一"); Runnable mt2 = new MyThread("线程二"); Runnable mt3 = new MyThread("线程三"); // 多线程启动 new Thread(mt1).start(); new Thread(mt2).start(); new Thread(mt3).start(); } }
运行情况:
以上程序实例化三个继承Runnable接口的MyThread类,然后通过Thread类的一个构造函数
public Thread(Runnable target)
分别实例化Thread类,再利用start()方法实现多线程。Thread方案实现和Runnable方案实现的区别:
// Thread类定义: public class Thread extends Object implements Runnable {}
也就是说,Thread类是Runable接口的子类,通过直接覆写Thread类的run方法实际上依然是覆写Runnable接口内的run方法,其实本质上是没有区别的,但是利用Runnable方案实现更加能体现面向对象思维,有点类似于代理设计模式。
2.3 Callable接口实现多线程
使用Runnable接口实现的多线程可以避免单继承的局限,但是还有一个问题就是run方法没有返回值,为了解决这个问题,所以提供了一个Callable接口
java.util.concurrent.Callable
其定义如下:
@FunctionalIterface public interface Callable<T>{ public T call() throws Exception; }
FutureTask类常用方法:
import java.util.concurrent.ExecutionException; // 导入ExecutionException异常包 public FutureTask(Callable<T> callable) // 构造函数:接收Callable接口实例 public FutureTask(Runable runnable,T result) // 构造函数:接收Runnable接口实例,同时指定返回结果类型 public T get() throws InterruptedException,ExecutionException // 取得线程操作返回结果
Thread类的一个构造方法:
public Thread(FutureTask<T> futuretask) //构造方法:接收FutureTask实例化对象
定义线程主体类:
import java.util.concurrent.Callable; public class MyThread implements Callable<Integer>{ private String name; public MyThread(String name) { this.name = name; } @Override public Integer call(){ Integer sum = 0; for(int i = 0 ; i < 500;i++) { System.out.println(this.name + i); sum += i; } return sum; } }
多线程启动:
import java.util.concurrent.FutureTask; public class testThread { public static void main(String[] args) throws Exception{ // 实例化继承Callable接口的MyThread类 MyThread mt1 = new MyThread("线程一"); MyThread mt2 = new MyThread("线程二"); MyThread mt3 = new MyThread("线程三"); // FutureTask类接收继承Callable接口的MyThread的实例 FutureTask<Integer> ft1 = new FutureTask<Integer>(mt1); FutureTask<Integer> ft2 = new FutureTask<Integer>(mt2); FutureTask<Integer> ft3 = new FutureTask<Integer>(mt3); // 启动多线程 new Thread(ft1).start(); new Thread(ft2).start(); new Thread(ft3).start(); System.out.println(ft1.get()); System.out.println(ft2.get()); System.out.println(ft3.get()); } }
运行情况:
通过以上代码容易了解到,Callable接口实现采用泛型技术实现,继承需要重写call方法,再通过FutureTask包装器包装,传入后实例化Thread类实现多线程。
其中FutureTask类是Runnable接口的子类,所以才可以利用Thread类的start方法启动多线程,读者可以将call方法假设为有返回值的run方法。2.3 多线程运行状态
任意线程具有5种基本的状态:
1.创建状态
实现Runnable接口和继承Thread类创建一个线程对象后,该线程对象处于创建状态,此时它已经有了内存空间和其它资源,但他还是处于不可运行的。
2.就绪状态
新建线程对象后,调用该线程的start方法启动该线程。启动后,进入线程队列排队,由CPU调度服务。
3.运行状态
就绪状态的线程获得处理器的资源时,线程就进入了运行状态,此时将自动调用run方法。
4.阻塞状态
正在运行的线程在某些特殊情况下,如:当前线程调用sleep、suspend、wait等方法时,运行在当前线程里的其它线程调用join方法时,以及等待用户输入的时候。只有当引起阻塞原因消失后,线程才能进入就绪状态。
5.终止状态
当线程run方法运行结束后,或者主线程的main()方法结束后,线程才能处于终止状态,线程一旦死亡就不能复生。
3. 多线程常用操作方法
- 线程的命名与取得
- 线程休眠方法
- 线程中断方法
- 线程强制执行
- 线程让步
- 线程优先级
3.1 线程的命名和获取
线程是不确定的运行状态,名称就是线程的主要标记。因此,需要注意的是,对于线程的名字一定要在启动之前设置进程名称,不建议对已经启动的线程,进行更改名称,或者为不同线程设置重名。
其主要方法:
public Thread(Runnable runnable,String name) //构造函数:实例化线程对象,为线程对象设置名称 public final void setName(String name) // 普通函数:设置线程名字 public final String getName() // 普通函数:获取线程名字
观察线程命名操作:
class MyThread implements Runnable{ @Override public void run() { System.out.println( Thread.currentThread().getName()); System.out.println( Thread.currentThread().getName()); } } public class ThreadDemo { public static void main(String[] args) { MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); MyThread mt3 = new MyThread(); new Thread(mt1,"线程一").start(); new Thread(mt2,"线程二").start(); new Thread(mt3).start(); // 使用默认的线程名称 mt1.run(); // 这里直接查看main方法的线程名称 } }
运行情况:
需要注意的是主方法也是进程的一个线程。3.2 线程休眠
sleep方法定义在java.lang.Thread中,由Thread.sleep()调用实现。其作用是需要暂缓线程的执行速度,则可以让当前线程休眠,即当前线程从“运行状态”进入到“阻塞状态”。sleep方法会指定休眠时间,线程休眠的时间会大于或等于该休眠时间,该线程会被唤醒,此时它会由“阻塞状态”变成“就绪状态”,然后等待CPU的调度执行。
其主要方法:
public static void sleep(long millis) throws InterruptedException // 普通函数:设置休眠时间的毫秒数 public static void sleep(long millis,int nanos) throws InterruptedException // 普通函数:设置休眠毫秒数和纳秒数
定义线程主体类:
public class MyThread implements Runnable{ @Override public void run() { for(int i = 0 ; i<90 ; i++) { System.out.println(Thread.currentThread().getName() + " 正在工作中……" + i ); if(i == 20) { try { System.out.println(Thread.currentThread().getName() + " 等一会儿就要休息五秒钟了……"); Thread.sleep(5000); // 当前线程休眠五秒钟 System.out.println(Thread.currentThread().getName() + " 已经休息五秒钟了……"); }catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + " 休眠被打扰了……"); } } } } }
观察线程休眠操作:
public class ThreadDemo { public static void main(String[] args){ MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); MyThread mt3 = new MyThread(); new Thread(mt1,"线程一").start(); new Thread(mt2,"线程二").start(); new Thread(mt3,"线程三").start(); } }
运行情况:
由以上结果容易了解到,休眠期间该线程并没有工作,而其他未休眠的线程则在继续工作。3.3 线程中断
interrupt方法定义在java.lang.Thread中,由Thread.interrupt()调用实现。该方法将会设置该线程的中断状态位,即设置为true,中断的结果线程是终止状态、还是阻塞状态或是继续运行至下一步,就取决于该程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(即中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程。
其主要方法:
// 以下均为Thread类的方法 public boolean isInterrupted() //普通函数:判断线程是否被中断 public void interrupt() //普通函数:中断线程执行
定义线程主体类:
public class MyThread implements Runnable { @Override public void run() { int i = 0; while(true) { System.out.println(Thread.currentThread().getName() + " 正在努力工作中……" + i++); try { System.out.println(Thread.currentThread().getName() + " 准备休息5秒钟了……"); Thread.sleep(5000); // 休眠5秒钟 System.out.println(Thread.currentThread().getName() + " 已经休息5秒钟了……"); }catch(InterruptedException e) { System.out.println(Thread.currentThread().getName() + " 被打扰了,不想工作了……"); break; } } } }
观察线程中断操作:
public class ThreadDemo { public static void main(String[] args) throws Exception{ Runnable mt1 = new MyThread(); Runnable mt2 = new MyThread(); Runnable mt3 = new MyThread(); Thread thread1 = new Thread(mt1,"线程一"); //线程一就绪 Thread thread2 = new Thread(mt2,"线程二"); //线程二就绪 Thread thread3 = new Thread(mt3,"线程三"); //线程三就绪 thread1.start(); //线程一启动 thread2.start(); //线程二启动 thread3.start(); //线程三启动 // 以下通过利用main线程控制 线程一 中断 Thread.sleep(6000); //使main方法先休眠6秒钟,即让子线程先运行6秒钟 if(!thread1.isInterrupted()) { System.out.println("吵闹~~~"); thread1.interrupt(); //中断线程一的执行 } } }
运行情况:
以上代码线程一在休眠期间被中断,然后直接break,也就是说以后就只有线程二和三工作了。这里每当中断执行都会产生InterruptedException异常。3.4 线程强制执行
join方法定义在java.lang.Thread中,由Thread.join()调用实现。多线程启动后会交替进行资源抢占和线程体执行,如果此时某些线程异常重要,也就是说这个对象需要优先执行完成,则可以设置为线程强制执行,待其完成后其它线程继续执行。
其主要方法:
// Thread类方法 public final void join() throws InterruptedException //普通函数:强制执行
定义线程主体类:
public class MyThread implements Runnable{ private Thread thread = null; public MyThread() {} public MyThread(Thread thread) { this.thread = thread; } @Override public void run() { for(int i = 0; i<50 ; i++) { if(i >= 20 && i <= 25) { try { System.out.println(thread.getName() + "被迫参与 " + Thread.currentThread().getName() + " 的工作了……" +i); thread.join(); }catch(InterruptedException e) { e.printStackTrace(); } } // 以下语句不管上面判断语句是否执行都会执行的 System.out.println(Thread.currentThread().getName() + " 正在工作中……" +i); } } }
观察线程强制执行操作:
public class ThreadDemo { public static void main(String[] args) { Thread mainThread = Thread.currentThread(); MyThread mt1 = new MyThread(mainThread); Thread thread1 = new Thread(mt1,"子线程"); thread1.start(); for(int i = 0 ;i<20;i++) { try { Thread.sleep(1000); // 每次main线程休眠1秒 System.out.println(Thread.currentThread().getName() + "正在工作中……" + i); }catch(InterruptedException e) { e.printStackTrace(); } } System.out.println("我main线程已经完成了所有任务,从此无法再复生了……"); } }
运行情况:
由以上结果容易了解到,当子线程经行到第20次时,开始强制执行main线程任务,直至全部完成为止,而且只能够完整地执行地一次。当main线程执行完毕后,子线程才能开始继续执行。
3.5 线程让步
yield方法定义在java.lang.Thread中,由Thread.yield()调用实现。多线程在彼此交替执行的时候往往需要进行资源的轮流抢占,如果某些不是很重要的线程抢占到资源但是又不急于执行时,就可以将当前的资源暂时让步出去,交给其它资源先执行。但是,因为yeild是将线程由“运行状态”转别为“就绪状态”,这样并不能保证在当前线程调用yield方法之后,其它具有相同优先级的线程就一定能获得执行权,也有可能是当前线程又进入到“运行状态”继续运行,因为还是要依靠CPU调度才可以。
其主要方法:
public static void yield() // 静态函数:线程让步
定义线程主体类:
public class MyThread implements Runnable{ private Thread thread = null; public MyThread () {} public MyThread(Thread thread) { this.thread = thread; } @Override public void run() { for(int i = 0 ; i<50 ; i++) { System.out.println(Thread.currentThread().getName() + " 正在工作中……" + i); if( i == 30) { System.out.println(Thread.currentThread().getName() + " 打算将工作交给 "+thread.getName() + "了……"); Thread.yield(); // 当前线程让步出去 System.out.println(Thread.currentThread().getName() + " 又想自己工作了……"); } } } }
观察线程让步操作:
public class ThreadDemo { public static void main(String[] args) { Thread mainThread = Thread.currentThread(); Runnable mt1 = new MyThread(mainThread); Thread thread1 = new Thread(mt1,"子线程"); thread1.start(); for(int i = 0 ; i < 40 ; i++) { *运行情况:* System.out.println(Thread.currentThread().getName() + " 正在工作中……" + i); } } }
运行情况:
由以上结果容易了解到,会短暂地将资源调度让给其它的线程,当然这并不是严格的,因为还是要CPU调度的。3.6 线程优先级
所有创造的线程都是子线程,所有的子线程在启动时都会保持同样的优先级权限,但是如果现在某些重要的线程希望可以优先抢占到资源并且先执行,就可以修改优先级权限来实现。
记住当线程的优先级没有指定时,所有线程都携带普通优先级。需要理解的是:
优先级用从1到10的范围的整数指定。10表示最高优先级,1表示最低优先级,5是普通优先级,也就是默认优先级。
优先级相对最高的线程在执行时被给予优先权限。但是不能保证线程在启动时就进入运行状态。
优先级越高越有可能先执行。其主要方法以及常量:
public static final int MAX_PRIORITY // 静态常量:最高优先级,数值为10 public static final int NORM_PRIORITY //静态常量:普通优先级,数值为5 public static final int MIN_PRIORITY // 静态常量:最低优先级,数值为1 public final void setPriority(int newPriority) // 普通函数:设置优先级 public final int getPriority() //普通函数:获取优先级
定义线程主体类:
public class MyThread implements Runnable{ @Override public void run() { for(int i = 0 ; i<50 ; i++) { System.out.println(Thread.currentThread().getName() + " 正在工作中……" + i); } } }
观察线程优先级操作:
public class ThreadDemo { public static void main(String[] args) { Runnable mt1 = new MyThread(); Runnable mt2 = new MyThread(); Thread thread1 = new Thread(mt1,"线程一"); Thread thread2 = new Thread(mt2,"线程二"); // 设置优先级 thread2.setPriority(Thread.MAX_PRIORITY); thread1.setPriority(Thread.MIN_PRIORITY); // 启动 thread1.start(); thread2.start(); } }
运行情况:
由以上可以看出,线程二执行的概率高于线程一的执行概率,前面的执行情况大致为交替执行,是因为它们的优先级均相等,默认都等于5。4. 线程的同步和锁死
当这样也导致了一个问题:在某一时刻,这一份资源在某一个线程发生改变时,其它线程正在执行的操作也会受到其影响。
如下程序为售票员售票代码以及结果:
定义线程主体类:
public class MyThread implements Runnable{ private int ticket = 10; @Override public void run() { while(true) { if(ticket<0) { System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket); break; } try { Thread.sleep(1000); // 延迟1秒,使得ticket可以被其它线程充分改变(可能此时的ticket小于等于0了) }catch(InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 正在售票,还剩余票数为:" + ticket--); } } }
观察售票状态:
public class ThreadDemo { public static void main(String[] args) { // 一份资源 Runnable mt1 = new MyThread(); // 共享同一份资源 new Thread(mt1,"售票员A").start(); new Thread(mt1,"售票员B").start(); new Thread(mt1,"售票员C").start(); } }
运行情况:
以上结果就很好地说明了多线程的不安全问题,票数本来应该大于等于0的,但是因为某一个A线程延迟,由于共享资源,此时可能其它某个线程已经将票数售完(ticket-- == 0),然后线程A继续执行就会使得票数小于0。要解决类似于上面的问题,就需要引入线程同步和死锁概念。
4.1 线程同步
解决数据共享问题必须使用同步,所谓的同步就是指多个线程在同一个时间段内只能有一个线程执行指定的代码,其他线程要等待此线程完成之后才可以继续进行执行,在Java中提供有
synchronized
关键字以实现同步处理,同步的关键是要为代码加上“锁”。而锁的操作有三种:
1.同步代码块
2.同步方法
3.Lock实现同步代码块实现
使用方式:
synchronized(需要同步的对象){ 需要同步的操作 }
定义线程主体类:
public class MyThread implements Runnable{ private int ticket = 10; @Override public void run() { while(true) { // 同步代码块 synchronized(this) { if(ticket<0) { System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket); break; } try { Thread.sleep(10); // 延迟0.01秒,使得ticket可以被其它线程充分改变(可能此时的ticket小于等于0了) }catch(InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 正在售票,还剩余票数为:" + ticket--); } } } }
观察售票状态:
public class ThreadDemo { public static void main(String[] args) { // 一份资源 Runnable mt1 = new MyThread(); // 共享同一份资源 new Thread(mt1,"售票员A").start(); new Thread(mt1,"售票员B").start(); new Thread(mt1,"售票员C").start(); } }
运行情况:
读者从上面代码也容易了解到,虽然它的确起到了安全的作用,但是执行的效率却下降了,因为每次都只有一个线程才能访问同步代码块。同步方法实现
使用方式:
利用函数包装的形式实现,如下: 修饰符 synchronized 返回类型 函数名()
定义线程主体类:
public class MyThread implements Runnable{ private int ticket = 10; @Override public void run() { while(this.sale()) {} } public synchronized boolean sale() { if(ticket<0) { System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket); return false; } try { Thread.sleep(10); // 延迟0.01秒,使得ticket可以被其它线程充分改变(可能此时的ticket小于等于0了) }catch(InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 正在售票,还剩余票数为:" + ticket--); return true; } }
观察售票状态:
public class ThreadDemo { public static void main(String[] args) { // 一份资源 Runnable mt1 = new MyThread(); // 共享同一份资源 new Thread(mt1,"售票员A").start(); new Thread(mt1,"售票员B").start(); new Thread(mt1,"售票员C").start(); } }
运行情况:
Lock锁实现
定义线程主体类:
package cn.wu; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MyThread implements Runnable{ private int ticket = 10; private final Lock lock = new ReentrantLock(); @Override public void run() { while(this.sale()) {} } public boolean sale() { lock.lock(); try{ if(ticket<0) { System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket); return false; } Thread.sleep(200); System.out.println(Thread.currentThread().getName() + " 正在售票,还剩余票数为:" + ticket--); }catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); } return true; } }
观察售票状态:
public class ThreadDemo { public static void main(String[] args) { // 一份资源 Runnable mt1 = new MyThread(); // 共享同一份资源 new Thread(mt1,"售票员A").start(); new Thread(mt1,"售票员B").start(); new Thread(mt1,"售票员C").start(); } }
运行情况:
4.2 线程死锁
所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去,死锁的操作一般是在程序运行时候才有可能出现,死锁是在多线程开发中较为常见的一种问题,过多的同步就有可能出现死锁。
观察死锁状态:
class firstCorssBridge{ public synchronized void tell(secondCorssBridge scb) { System.out.println("张三告诉王五:我先过,你后过,否则你别想过这桥!"); scb.cross(); } // 以下函数不会执行 public synchronized void cross() { System.out.println("张三快快乐乐地过桥了……"); } } class secondCorssBridge{ public synchronized void tell(firstCorssBridge fcb) { System.out.println("王五告诉张三:我先过,你后过,否则你别想过这桥!"); fcb.cross(); } // 以下函数不会执行 public synchronized void cross() { System.out.println("王五快快乐乐地过桥了……"); } } public class DeadLock implements Runnable{ private firstCorssBridge fcb = new firstCorssBridge(); private secondCorssBridge scb = new secondCorssBridge(); public DeadLock() { // 启动线程 并执行以下语句 new Thread(this).start(); // 会运行run函数 fcb.tell(scb); // 运行到里面时 fcb会等待scb } @Override public void run() { scb.tell(fcb); // 运行到里面时 scb会等待fcb } public static void main(String[] args) { new DeadLock(); } }
运行情况:
上面红色的说明:两者已经处于相互等待状态,后续代码并不会执行。如果读者不太理解上面的代码,可以在
new Thread(this).start(); // 会运行run函数
语句的下面加上:try { Thread.sleep(100); // 休眠0.1秒钟 }catch(InterruptedException e) { e.printStackTrace(); }
结果就为:
这实际上是“锁中有锁”的情况。5. 后台守护线程
Java中的线程分为两类,用户线程和守护线程。守护线程(Daemon)是一种运行在后台的线程服务线程,当用户线程存在时,守护线程可以同时存在,但是,当用户线程不存在时,守护线程会全部消失。
主要操作方法:
public final setDaemon(boolean on) throws Exception // 普通函数:是否设置为守护线程 public final boolean isDaemon() //普通函数: 判断是否为
观察守护线程操作:
class MyThread implements Runnable{ private int times; public MyThread(int times) { this.times = times; } @Override public void run() { for(int i = 0 ; i<times;i++) { if(Thread.currentThread().isDaemon()) { try { Thread.sleep(10); // 如果是守护线程,则休眠0.01秒钟 }catch(InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + " 正在工作中……"+i); } } } public class testDemo { public static void main(String[] args) { MyThread mt1 = new MyThread(4); MyThread mt2 = new MyThread(100); //守护线程的循环次数远多于用户线程 Thread thread1 = new Thread(mt1,"用户线程"); Thread thread2 = new Thread(mt2,"守护线程"); thread2.setDaemon(true); //thread2设置为守护线程 thread1.start(); thread2.start(); } }
运行情况:
由以上可以了解到,守护线程已经提前结束了,原因是main线程等用户线程全部消失了。守护线程的应用场景
在主线程关闭后无需手动关闭守护线程,因为会自动关闭,避免了麻烦,Java垃圾回收线程就是一个典型的守护线程,简单粗暴地可以理解为所有为线程服务而不涉及资源的线程都能设置为守护线程。
6. 线程池
线程池的概念
容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程的操作,无需反复创建线程而消耗过多的资源。为何引入线程池?
如果并发的线程数量过多,并且每个线程都是执行一个时间很短的任务就结束,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要消耗时间,线程也属于宝贵的系统资源,因此,线程池就是为了能使线程可以复用而创建的。线程池的好处?
- 降低资源的消耗,减少创建和销毁线程的次数,每个工作线程都可以被重复使用,可执行多个任务
- 提高响应速度,不需要频繁地创建线程,如果有线程可以直接使用,避免了系统僵死
- 提高线程的可管理性
核心思想:线程复用
Runnable简单实例:
package cn.wu; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test { public static void main(String[] args) { // 1.创建一个线程池,指定线程的数量为4 ExecutorService pools = Executors.newFixedThreadPool(4); // 2.添加线程任务 Runnable target = new MyRunnable(); pools.submit(target); // 第一次提交任务,此时创建新线程 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<10 ; i++) { System.out.println(Thread.currentThread().getName()+"正在执行任务… "+i); } } }
结果:
Callable简单实例:package cn.wu; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Test { public static void main(String[] args) { // 1.创建一个线程池,指定线程的数量为4 ExecutorService pools = Executors.newFixedThreadPool(4); try{ long start = System.currentTimeMillis(); // 2.添加线程任务 Future<String> t1 = pools.submit(new MyCallable(100000)); // 提交求出1-100000和的线程任务 Future<String> t2 = pools.submit(new MyCallable(200000)); // 提交求出1-200000和的线程任务 Future<String> t3 = pools.submit(new MyCallable(300000)); // 提交求出1-300000和的线程任务 Future<String> t4 = pools.submit(new MyCallable(400000)); // 提交求出1-400000和的线程任务 Future<String> t5 = pools.submit(new MyCallable(500000)); // 提交求出1-500000和的线程任务 System.out.println(t1.get()); System.out.println(t2.get()); System.out.println(t3.get()); System.out.println(t4.get()); System.out.println(t5.get()); long end = System.currentTimeMillis(); System.out.println("采用多线程所耗时间为:"+(end-start)*1.0/1000+"s"); start = System.currentTimeMillis(); long sum = 0; for(int i = 1 ; i<=100000 ; i++) { sum += i; } System.out.println("最终结果为:"+sum); sum = 0; for(int i = 1 ; i<=200000 ; i++) { sum += i; } System.out.println("最终结果为:"+sum); sum = 0; for(int i = 1 ; i<=300000 ; i++) { sum += i; } System.out.println("最终结果为:"+sum); sum = 0; for(int i = 1 ; i<=400000 ; i++) { sum += i; } System.out.println("最终结果为:"+sum); sum = 0; for(int i = 1 ; i<=500000 ; i++) { sum += i; } System.out.println("最终结果为:"+sum); end = System.currentTimeMillis(); System.out.println("采用单线程所耗时间为:"+(end-start)*1.0/1000+"s"); }catch(Exception e) { e.printStackTrace(); } } } class MyCallable implements Callable<String> { private int num; public MyCallable(int num) { this.num = num; } @Override public String call() throws Exception { long sum = 0; for(int i = 1 ; i <= num ; i++) { sum += i; } return Thread.currentThread().getName()+ "任务执行的最终结果为:"+sum; } }
结果:
-
JAVA多线程程序开发基础知识
2016-03-31 22:52:11JAVA多线程基础 概念-程序、进程与多任务 程序(program)是对数据描述与操作的代码的集合,是应用程序执行的脚本。 进程(process)是程序的一次执行过程,是系统运行程序的基本单位。程序是静态的,进程...JAVA多线程基础
概念-程序、进程与多任务
程序(program)是对数据描述与操作的代码的集合,是应用程序执行的脚本。
进程(process)是程序的一次执行过程,是系统运行程序的基本单位。程序是静态的,进程是动态的。系统运行一个程序即是一个进程从创建、运行到消亡的过程。
多任务(multi task)在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程。线程
线程(thread):比进程更小的运行单位,是程序中单个顺序的流控制。一个进程中可以包含多个线程。
简单来讲,线程是一个独立的执行流,是进程内部的一个独立执行单元,相当于一个子程序。
一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。
操作系统给每个线程分配不同的CPU时间片,在某一时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行。创建多线程
每个Java程序启动后,虚拟机将自动创建一个主线程
可以通过以下两种方式自定义线程类:
创建 java.lang.Thread 类的子类,重写该类的 run方 法
创建 java.lang.Runnable接 口的实现类,实现接口中的 run 方法继承 Thread 类
Thread:代表一个线程类
Thread 类
Thread类中的重要方法:
run方法:包括线程运行时执行的代码,通常在子类中重写它。
start方法:启动一个新的线程,然后虚拟机调用新线程的run方法
Thread 类代码示例:线程执行流程
创建多线程
问题:要定义的线程类已经显式继承了一个其他的类怎么办?
答:实现Runnable接口Runnable 接口
Runnable 接口中只有一个未实现的 run 方法,实现该接口的类必须重写该方法。
Runnable 接口与 Thread 类之间的区别
Runnable 接口必须实现 run 方法,而 Thread 类中的run 方法是一个空方法,可以不重写
Runnable 接口的实现类并不是真正的线程类,只是线程运行的目标类。要想以线程的方式执行 run 方法,必须依靠 Thread 类
Runnable 接口适合于资源的共享线程的生命周期
线程的生命周期:
- 指线程从创建到启动,直至运行结束
- 可以通过调用 Thread 类的相关方法影响线程的运行状态线程的运行状态
1、 新建(New)
当创建了一个Thread对象时,该对象就处于“新建状态”
没有启动,因此无法运行
2、 可执行(Runnable)
其他线程调用了处于新建状态线程的start方法,该线程对象将转换到“可执行状态”
线程拥有获得CPU控制权的机会,处在等待调度阶段。
3、 运行(Running)
处在“可执行状态”的线程对象一旦获得了 CPU 控制权,就会转换到“执行状态”
在“执行状态”下,线程状态占用 CPU 时间片段,执行run 方法中的代码
处在“执行状态”下的线程可以调用 yield 方法,该方法用于主动出让 CPU 控制权。线程对象出让控制权后回到“可执行状态”,重新等待调度。4、 阻塞(Blocking)
线程在“执行状态”下由于受某种条件的影响会被迫出让CPU控制权,进入“阻塞状态”。
进入阻塞状态的三种情况
- 调用sleep方法
1、 public void sleep(long millis)
2、 Thread类的sleep方法用于让当前线程暂时休眠一段时间
参数 millis 的单位是毫秒- 调用join方法
处在“执行状态”的线程如果调用了其他线程的 join 方法,将被挂起进入“阻塞状态”
目标线程执行完毕后才会解除阻塞,回到 “可执行状态”执行I/O操作
线程在执行过程中如果因为访问外部资源(等待用户键盘输入、访问网络)时发生了阻塞,也会导致当前线程进入“阻塞状态”。
4.1、 解除阻塞
睡眠状态超时
调用 join 后等待其他线程执行完毕
I/O 操作执行完毕
调用阻塞线程的 interrupt 方法(线程睡眠时,调用该线程的interrupt方法会抛出InterruptedException)5、死亡(Dead)
死亡状态(Dead):处于“执行状态”的线程一旦从run方法返回(无论是正常退出还是抛出异常),就会进入“死亡状态”。
已经“死亡”的线程不能重新运行,否则会抛出IllegalThreadStateException
可以使用 Thread 类的 isAlive 方法判断线程是否活着线程调度
线程调度
按照特定机制为线程分配 CPU 时间片段的行为
Java程序运行时,由 Java 虚拟机负责线程的调度
线程调度的实现方式
分时调度模型:让所有线程轮流获得CPU的控制权,并且为每个线程平均分配CPU时间片段
抢占式调度模型:选择优先级相对较高的线程执行,如果所有线程的优先级相同,则随机选择一个线程执行 。Java虚拟机采用此种调度模型。线程的优先级
Thread类提供了获取和设置线程优先级的方法
getPriority:获取当前线程的优先级
setPriority:设置当前线程的优先级
Java语言为线程类设置了10个优先级,分别使用1~10内的整数表示 ,整数值越大代表优先级越高。每个线程都有一个默认的优先级,主线程的默认优先级是5。
Thread类定义的三个常量分别代表了几个常用的优先级:
MAX_PRIORITY::代表了最高优先级10
MIN_PRIORITY::代表了最低优先级1
NORM_PRIORITY::代表了正常优先级5
setPriority 不一定起作用,在不同的操作系统、不同的 JVM 上,效果也可能不同。操作系统也不能保证设置了优先级的线程就一定会先运行或得到更多的CPU时间。
在实际使用中,不建议使用该方法线程同步
问题:通过多线程解决售票问题。
非线程安全示例:
TicketWindow2.java复制内容到剪贴板- /**
- * 售票窗口
- */
- public class TicketWindow2 implements Runnable {
- //票数
- int ticketNum = 10;
- private boolean isNext(){
- //是否售完标识
- boolean f = false;
- if (ticketNum > 0) {
- //每次少一张票
- ticketNum--;
- f = true;
- }
- try {
- /**
- * Thread-1 暂停的时候ticketNum=9, Thread2进来也执行ticketNum--后ticketNum=9
- * 此时Thread-1 在1秒之后 执行后面的打印,会直接打印出
- * 窗口1 剩余 8 张票...
- * 窗口2 剩余 8 张票...
- */
- Thread.currentThread().sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (ticketNum == 0)
- f = false;
- if (!f)
- System.out.println(Thread.currentThread().getName() + " 票已售完 ");
- else
- System.out.println(Thread.currentThread().getName() + " 剩余 " + ticketNum + " 张票...");
- return f;
- }
- @Override
- public void run() {
- for (;ticketNum > 0 ;){
- //执行售票
- isNext();
- }
- }
- }
测试方法
复制内容到剪贴板- public static void main(String[] a ){
- TicketWindow2 tw2 = new TicketWindow2();
- Thread t1 = new Thread(tw2,"窗口1");
- Thread t2 = new Thread(tw2,"窗口2");
- t2.start();
- t1.start();
- }
结果:
窗口1 剩余 8 张票…
窗口2 剩余 8 张票…
窗口1 剩余 6 张票…
窗口2 剩余 6 张票…
窗口1 剩余 4 张票…
窗口2 剩余 3 张票…
窗口1 剩余 2 张票…
窗口2 剩余 1 张票…
窗口1 票已售完
窗口2 票已售完结果已反映了一切问题。多窗口售票是不安全的有问题的。那如何解决这些问题???
线程安全
多线程应用程序同时访问共享对象时,由于线程间相互抢占CPU的控制权,造成一个线程夹在另一个线程的执行过程中运行,所以可能导致错误的执行结果。
Synchronized 关键字
为了防止共享对象在并发访问时出现错误,Java中提供了“synchronized”关键字。
1、 synchronized关键字
确保共享对象在同一时刻只能被一个线程访问,这种处理机制称为“线程同步”或“线程互斥”。Java中的“线程同步”基于“对象锁”的概念
2、 使用 synchronized 关键字
修饰方法:被“synchronized”关键字修饰的方法称为”同步方法”
当一个线程访问对象的同步方法时,被访问对象就处于“锁定”状态,访问该方法的其他线程只能等待,对象中的其他同步方法也不能访问,但非同步方法则可以访问复制内容到剪贴板- //定义同步方法
- public synchronized void methd(){
- //方法实现
- }
3、 使用 ”synchronized” 关键字:修饰部分代码,如果只希望同步部分代码行,可以使用“同步块”
复制内容到剪贴板- //同步块
- synchronized(obj){
- //被同步的代码块
- }
同步块的作用与同步方法一样,只是控制范围有所区别
修改示例
重新修改售票的例子,将isNext方法改为同步方法
复制内容到剪贴板- //票数
- int ticketNum = 100;
- /**
- * 同步方法
- */
- private synchronized boolean isNext(){
- /**
- * 同步代码块
- */
- synchronized (this) {
- boolean f = false;
- if (ticketNum > 0) {
- ticketNum--;
- f = true;
- }
- try {
- /**
- * Thread-1 暂停的时候ticketNum=9, Thread2进来也执行ticketNum--后ticketNum=9
- * 此时Thread-1 在1秒之后 执行后面的打印,会直接打印出
- * 窗口1 剩余 8 张票...
- * 窗口2 剩余 8 张票...
- */
- Thread.currentThread().sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (ticketNum == 0)
- f = false;
- if (!f)
- System.out.println(Thread.currentThread().getName() + " 票已售完 ");
- else
- System.out.println(Thread.currentThread().getName() + " 剩余 " + ticketNum + " 张票...");
- return f;
- }
- }
测试:
为了实现多线程的结果 我模拟了4个售票窗口,100张票
测试方法复制内容到剪贴板- public static void main(String[] a ){
- /**
- * 这里可以看出 TicketWindow2 为线程共享参照对象,
- * 也就是说,如果需要同步的话,那synchronize(参照对象)中的参照对象即为:TicketWindow2
- */
- TicketWindow2 tw2 = new TicketWindow2();
- Thread t1 = new Thread(tw2,"窗口1");
- Thread t2 = new Thread(tw2,"窗口2");
- Thread t3 = new Thread(tw2,"窗口3");
- Thread t4 = new Thread(tw2,"窗口4");
- t2.start();
- t1.start();
- t4.start();
- t3.start();
- }
结果:
窗口2 剩余 99 张票…
窗口2 剩余 98 张票…
…
窗口2 剩余 84 张票…
窗口3 剩余 83 张票…
窗口3 剩余 82 张票…
…
窗口3 剩余 20 张票…
窗口4 剩余 19 张票…
…
窗口4 剩余 1 张票…
窗口4 票已售完
窗口1 票已售完
窗口3 票已售完
窗口2 票已售完线程通信
wait()方法:
中断方法的执行,使本线程等待,暂时让出 cpu 的使用权,并允许其他线程使用这个同步方法。
notify()方法:
唤醒由于使用这个同步方法而处于等待线程的 某一个结束等待
notifyall()方法:
唤醒所有由于使用这个同步方法而处于等待的线程结束等待练习
通过交叉打印,练习线程通信方法
PrintWords.java复制内容到剪贴板- public class PrintWords implements Runnable{
- private boolean a=true,b=false;
- private char abc = 'a';
- private synchronized void doPrint(){
- if (abc <= 'z') {
- String name = Thread.currentThread().getName();
- System.out.println("Thread- " + name + " : " + abc);
- abc++;
- //等下个线程进来就可以换新上个等待的线程
- notifyAll();
- try {
- //当前线程等待
- wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- if (abc >= 'z'){
- notifyAll();
- }
- }
- @Override
- public void run() {
- while (abc <= 'z') {
- doPrint();
- }
- }
- public static void main(String[] a){
- Runnable p = new PrintWords();
- Thread ta = new Thread(p,"a");
- Thread tb = new Thread(p,"b");
- ta.start();
- tb.start();
- }
- }
测试结果
Thread- a : a
Thread- b : b
Thread- a : c
Thread- b : d
…
Thread- a : k
Thread- b : l
…
Thread- b : z -
Java 多线程(超详细)
2021-01-12 21:14:38多线程学习思路:为什么学习线程?为了解决CPU利用率问题,提高CPU利用率。 =》 什么是进程?什么是线程? =》 怎么创建线程?有哪几种方式?有什么特点? =》 分别怎么启动线程? =》 多线程带来了数据安全问题,该...多线程学习思路:为什么学习线程?为了解决CPU利用率问题,提高CPU利用率。 =》 什么是进程?什么是线程? =》 怎么创建线程?有哪几种方式?有什么特点? =》 分别怎么启动线程? =》 多线程带来了数据安全问题,该怎么解决? =》 怎么使用synchronized(同步)决解? =》使用同步可能会产生死锁,该怎么决解? =》 线程之间是如何通信的? =》 线程有返回值吗?该如何拿到? =》 怎么才能一次性启动几百上千个的线程?
线程的概念
什么是进程
进程是操作系统中正在执行的不同的应用程序,例如:我们可以同时打开Word和记事本什么是线程
线程是一个应用程序进程中不同的执行路径,例如:我们的WEB服务器,能够为多个用户同时提供请求服务
进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。线程总是在某个进程环境中创建的,而且它的整个寿命期都在该进程中。– Java的多线程
• Java 中的多线程是通过java.lang.Thread类来实现的.
• 一个Java应用程序java.exe,其实至少有三个线程: main()主线程, gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
• 使用多线程的优点。
– 背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短(因为单线程的可以减少cup的调度消耗的时间),为何仍需多线程呢?
– 多线程程序的优点:
1.提高应用程序的响应。对图形化界面更有意义,可增强用户体验。同时做多个事情。比如:一边听歌、一边写代码。
2.提高计算机系统CPU的利用率。
3.改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。
• 何时需要多线程
– 程序需要同时执行两个或多个任务。
– 需要一些后台运行的程序时。比如:Java后台运行的GC功能。
主线程
– 概念
• 即使Java程序没有显示的来声明一个线程,Java也会有一个线程存在该线程叫做主线程
• 可以调用Thread.currentThread()来获得当前线程线程的创建方法
有两种方法来创建线程
• 继承Thread类
– MyThread extends Thread
» 需要覆盖run方法• 实现Runnable接口
– Runnable 中有一个方法run用来定义线程执行代码
– public void run();后面还会介绍两种,一共是四种创建方式。
线程的启动和终止
线程的启动
• 线程的启动需要调用Thread的start方法,不能直接调用run方法,如果直接调用run方法相当于方法调用。
线程的终止
• 当run方法返回,线程终止,一旦线程终止后不能再次启动。
• 线程的终止可以调用线程的interrupt方法,但该方法不是最佳方法,最好是设置一个标记来控制线程的终止。
注意事项:一个线程只能启动一次,不能多次启动同一个线程。线程控制基本方法
• Thread类的有关方法(1)
– void start():启动线程并执行对象的run()方法
– run():线程在被调度时执行的操作
– String getName():返回线程的名称
– void setName(String name):设置该线程名称
– static Thread currentThread():返回当前线程。
• 线程控制的基本方法
线程的优先级
– 线程的优先级越高占用CPU时间越长
– 最高为10级,最低为1级,默认是5级
线程的状态转换
线程创建的选择
• 创建线程的两种方式。
开发中:优先选择实现Runnable 接口的方式来创建线程。
– 1.实现接口的方式没有类的单继承性的局限性。
– 2.实现接口的方式更适合来处理多个线程有共享数据的情况。线程的练习
创建三个窗口卖票,总票数为100张。(通过线程的两种实现方式分别来完成)
方式一://练习,100张票三人卖 public class WindowTest2 implements Runnable { private int ticket = 100; @Override public void run() { while (true) { if (ticket <= 0) { return; } System.out.println(Thread.currentThread().getName() + "-->售出第:" + (101 - ticket) + "票"); ticket--; try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { WindowTest2 wt1 = new WindowTest2(); //创建三个线程,并启动,通过的是同一个对象来创建,所以票数可以是非静态的 new Thread(wt1).start(); new Thread(wt1).start(); new Thread(wt1).start(); //线程安全问题:同一个时间,多个线程访问(修改)同一个对象,造成结果不可预测(混乱) //线程安全问题的条件:1.同一时间、2.多个线程一起访问、3操作的是同一个对象 /*输出的部分结果: * Thread-1-->售出第:1票 * Thread-0-->售出第:1票 * Thread-2-->售出第:1票 * Thread-1-->售出第:4票 * Thread-2-->售出第:4票 * Thread-0-->售出第:4票 * Thread-1-->售出第:7票 * Thread-2-->售出第:7票 * Thread-0-->售出第:7票 */ } }
方式二:
//练习,100张票三人卖 public class WindowTest1 extends Thread { private static int ticket = 100; @Override public void run() { while (true) { if (ticket <= 0) { return; } getTicket(); } } private static void getTicket() { if(ticket==0) return; System.out.println(Thread.currentThread().getName() + "-->售出第:" + (101 - ticket) + "票"); ticket--; try { sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { WindowTest1 wt1 = new WindowTest1(); WindowTest1 wt2 = new WindowTest1(); WindowTest1 wt3 = new WindowTest1(); wt1.start(); wt2.start(); wt3.start(); //线程安全问题:同一个时间,多个线程访问(修改)同一个对象,造成结果不可预测(混乱) //线程安全问题的条件:1.同一时间、2.多个线程一起访问、3操作的是同一个对象 /*输出的部分结果: * Thread-1-->售出第:1票 * Thread-0-->售出第:1票 * Thread-2-->售出第:1票 * Thread-1-->售出第:4票 * Thread-2-->售出第:4票 * Thread-0-->售出第:4票 * Thread-1-->售出第:7票 * Thread-2-->售出第:7票 * Thread-0-->售出第:7票 */ } }
在这里买票的三个窗口,会出现买了同一张票的问题,后面将会决解这个问题。
线程的同步
• 为什么需要线程同步:一个银行账号在同一时间不能接受多个线程的访问,因为这样会造成混乱
• 线程的同步
synchronized
线程安全问题:
1 同一时间
2 多个线程
3 操作同一个账号
就会出现混乱情况,这种由于多线程引发的混乱情况,我们就称他为:线程安全问题
如何解决呢?同步操作了解决线程问题。synchronized关键字
• synchronized 是Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
1、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
2、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
3、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
4、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
• 方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
1. 操作共享数据的代码,即为需要被同步的代码
2.共享数据:多个线程共同操作的变量。比如: ticket 就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。通过第一种同步方式修改售票问题
//练习,100张票三人卖 public class WindowTest4 implements Runnable { private int ticket = 100; @Override public void run() { while (true) { // 同步关键字括号中是同步监视器 if (ticket <= 0) { return; } getTicket(); } } private synchronized void getTicket() { if (ticket == 0) return; System.out.println(Thread.currentThread().getName() + "-->售出第:" + (101 - ticket) + "票"); ticket--; try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { WindowTest4 wt1 = new WindowTest4(); // 创建三个线程,并启动,通过的是同一个对象来创建,所以票数可以是非静态的 new Thread(wt1).start(); new Thread(wt1).start(); new Thread(wt1).start(); /*部分输出结果: * Thread-1-->售出第:1票 * Thread-1-->售出第:2票 * Thread-1-->售出第:3票 * Thread-1-->售出第:4票 * Thread-2-->售出第:5票 * Thread-2-->售出第:6票 * Thread-2-->售出第:7票 * Thread-0-->售出第:8票 * Thread-2-->售出第:9票 * Thread-2-->售出第:10票 * Thread-1-->售出第:11票 * Thread-1-->售出第:12票 */ } }
单例模式:懒汉模式的同步问题及解决
/* * 单例模式:懒汉模式 */ public class UserManager { // 懒汉模式:用的时候才创建,不用的时候为null private static UserManager instance; private int id; private String name; private UserManager() { } // 方式一:给方法加入synchronized关键字 // public static synchronized UserManager getInstance() { // // if(instance==null) { // instance = new UserManager(); // } // return instance; // } // 方式二:通过同步代码块的方式实现线程安全问题 // public static UserManager getInstance() { // // synchronized (UserManager.class) { // if (instance == null) { // instance = new UserManager(); // } // } // return instance; // } // 方式三:通过双检测机制实现对象的创建,更安全,效率更高 public static UserManager getInstance() { if (instance == null) { synchronized (UserManager.class) { if (instance == null) { instance = new UserManager(); } } } return instance; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
死锁
线程同步带来的问题:死锁
理解什么是死锁?
死锁问题的产生:
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
解决方法
➢通过逻辑算法来避免出现死锁。
➢尽量减少同步资源的定义。
➢尽量避免嵌套同步。线程的通信
• wait/notify/notifyAll
– wait():执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
– notify():执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的。
– notifyAll():执行此方法,就会唤醒所有被wait的线程。
• 说明:- wait(), notify(), notifyAll()三个方法必须使用在同步代码块或同步方法中。
- wait(), notify(), notifyAll三个方法的调用者必须是同步代码块或同步方法中的同步监视器否则,会出现IllegaLMonitorStateException异常.
经典案例:生成者、消费者问题
public class ProducerConsumer { public static void main(String[] args) { BaoziStack baoziStack = new BaoziStack(); Producer p1 = new Producer(baoziStack); Consumer c1 = new Consumer(baoziStack); p1.start(); c1.start(); } } // 包子类 class Baozi { int id; public Baozi(int id) { this.id = id; } @Override public String toString() { return "包子 : " + id; } } // 包子筐 class BaoziStack { Baozi[] bz = new Baozi[10]; int index = 0; // 装包子 public synchronized void pushBZ(Baozi baozi) { if (index >= bz.length) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } bz[index] = baozi; index++; notify(); } // 取包子 public synchronized Baozi popBZ() { if (index <= 0) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } index--; Baozi baozi = bz[index]; notify(); return baozi; } } // 生产者:生产包子,放到包子筐里 class Producer extends Thread { private BaoziStack baoziStack; public Producer(BaoziStack baoziStack) { this.baoziStack = baoziStack; } @Override public void run() { // 生产包子(一天生产100个包子) for (int i = 1; i <= 100; i++) { Baozi baozi = new Baozi(i); System.out.println("生产者生产了一个包子ID为: " + i); baoziStack.pushBZ(baozi); try { sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } } // 消费者 class Consumer extends Thread { private BaoziStack baoziStack; public Consumer(BaoziStack baoziStack) { this.baoziStack = baoziStack; } @Override public void run() { // 一天的消费量为100个包子 for (int i = 1; i <= 100; i++) { Baozi baozi = baoziStack.popBZ(); System.out.println("消费者消费了一个包子ID为:" + baozi.id + "的包子"); try { sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
线程拓展部分内容:
JDK5.0新增方式一:实现Callable接口
Runnable和Callable的区别:
1、Callable规定的方法是call(),Runnable规定的方法是run().
2、Callable的任务执行后可返回值,而Runnable的任务是不能有返回值。
3、call方法可以抛出异常,run方法不可以。
DK5.0新增方式二:使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。JDK5.0起提供了线程池相关API: ExecutorService 和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command):执行任务1命令,没有返回值,-般用来执行Runnable
< T > Future< T > submit(Callable< T > task):执行任务, 有返回值,一般来执行Callable
void shutdown():关闭连接池
Executors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool(): 创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor(): 创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n): 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
好处:
1.提高响应速度(减少创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理
corePoolSize:核心池的大小
maximumPoolsize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止线程池实例如下
newFixedThreadPool() 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
收工
感谢阅览~~~
看只是学习的一种输入途径而已,重要的是理解、实践和输出。输入和输出要保持好唷~~不然只有输入没有多上输出很快就会把学过的知识忘记了ヽ(*。>Д<)o゜ -
Java多线程超详解
2019-06-11 01:00:30随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。 那么话不多说,今天本帅将记录自己线程的学习。 线程的相关API //获取当前... -
Java多线程模拟售票程序,及线程安全问题
2017-05-02 20:54:24Java中线程部分知识中,售票程序非常经典。程序中也有一些问题存在! 需求:模拟3个窗口同时在售100张票。 问题1:为什么100张票被卖出了300张票? 原因:因为tickets是非静态的,非静态的成员变量数据是在每个... -
Java 多线程的应用场景
2022-05-01 09:49:54电影院新片首映,观影人数大量增加,为提高日营业额,线下售票窗口由原单窗口调整为3窗口,设计一段简单的程序模拟该售票过程。 -
深入浅出:Java多线程编程实战(一)
2018-08-21 15:45:33首先,整理出一张图概括了Java多线程的体系: 一、进程与线程 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 线程,有时被... -
java 多线程面试题及答案
2021-09-18 09:20:33并行和并发有什么区别?...1、进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)一个程序至少有一个进程,一个进程至少有一个线程。 2、进程有自己的独立地址空间,每启动一个进程,系统就. -
JAVA之多线程
2022-03-06 17:55:11就绪状态(Runnable) 当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,Java虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待CPU的使用权。 运行状态... -
编写一个Java 多线程程序,完成三个售票窗口同时出售20张票(如下图所示);
2019-07-05 21:17:35编写一个Java 多线程程序,完成三个售票窗口同时出售20张票(如下图所示); 程序分析:(1)票数要使用同一个静态值; (2)为保证不会出现卖出同一个票数,要java多线程同步锁。 设计思路: (1)创建一个站台类... -
java多线程程序性能调优 优化过程
2016-11-30 17:06:26我, 一多年c++开发,由于项目原因需要对一个性能底下的多线程java程序进行调优,百度google了几把,妈蛋,没有发现指导如何java线程调优的文章啊,都是一些java使用规范,我去,那我大java的开发工程师都是怎么调优... -
java多线程:5、在java程序中怎样保证多线程的运行安全?
2020-04-05 21:29:52文章目录在java程序中怎样保证多线程的运行安全? 在java程序中怎样保证多线程的运行安全? -
万字图解Java多线程
2020-09-06 14:45:07java多线程我个人觉得是javaSe中最难的一部分,我以前也是感觉学会了,但是真正有多线程的需求却不知道怎么下手,实际上还是对多线程这块知识了解不深刻,不知道多线程api的应用场景,不知道多线程的运行流程等等,... -
java 多线程并发查询mysql数据
2020-09-29 17:24:01程序猿学社的GitHub,欢迎Star github技术专题 本文已记录到github 文章目录前言需求思路代码 前言 用过mysql的朋友,对mysql性能应该有一定的感悟,数据...使用多线程并发查询每一天的数据在合并。 代码 /** . -
Java多线程面试题(面试必备)
2020-05-26 01:15:38文章目录一、多线程基础基础知识1. 并发编程1.1 并发编程的优缺点1.2 并发编程的三要素1.3 并发和并行有和区别1.4 什么是多线程,多线程的优劣?2. 线程与进程2.1 什么是线程与进程2.2 线程与进程的区别2.3 用户线程... -
Java多线程创建与运行
2021-11-10 14:06:37那么java里面是怎么实现多线程的呢? java实现了这样一个类,名叫Thread,是java的线程对象 进入到Thread源码中可以看到源码提供了两种创建多线程的方式 方式一:继承Thread类 class PrimeThread extends Thread { ... -
java多线程模拟售票
2022-01-28 23:23:49import java.util.concurrent.TimeUnit; /** * @author yeqv * @program A2 * @Classname Ticket * @Date 2022/1/28 23:04 * @Email w16638771062@163.com */ public class Ticket implements Runnable { //... -
Java多线程实现多用户与服务端Socket通信
2020-11-02 13:54:36Java多线程实现多用户与服务端Socket通信,类似QQ、微信、视频等客户端,多用户与服务器通信。详细记录服务端多线程的实现,目标是多用户(客户端)能够同时与服务器建立连接并通信,避免阻塞,进一步完善TCP的... -
JAVA多线程是什么
2020-08-31 23:53:23一、 什么是多线程: ...多线程:线程是进程内部比进程更小的执行单元(执行流|程序片段),每个线程完成一个任务,每个进程内部包含了多个线程每个线程做自己的事情,在进程中的所有线程共享该进程的资源; 主 -
Java多线程系列—多线程带来的问题(05)
2021-05-05 14:08:35多线程带来的问题 为什么需要多线程 其实说白了,时代变了,现在的机器都是多核的了,为了榨干机器最后的性能我们引入单线程。 为了充分利用CPU资源,为了提高CPU的使用率,采用多线程的方式去同时完成几件事情而不... -
Java多线程学习(吐血超详细总结)
2015-03-14 13:13:17本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 -
java程序龟兔赛跑(利用多线程)
2021-03-14 18:19:26java程序龟兔赛跑(利用多线程) /* * 题目:利用多线程实现龟兔赛跑 * * 分析:1.设置跑的米数 * 2.判断是否有人跑完 * 3.有人跑完,结束比赛 * 4.设置停顿毫秒数(让兔子),保证让乌龟赢 * * */ public class ... -
java 多线程操作数据库
2010-10-19 13:16:33一个java 多线程操作数据库应用程序!!! -
Java多线程(超详细!)
2021-05-12 17:00:591、什么是进程?什么是线程? 进程是:一个应用程序(1个进程是一个软件)。...最起码,现在的java程序中至少有两个线程并发, 一个是垃圾回收线程,一个是执行main方法的主线程。 3、进程和线程是什么关系? -
java多线程+Socket+Swing做的局域网聊天程序
2013-07-04 15:58:55上次传了一个,结果忘了传源码,只有图片效果,害的大家以为我是标题党。 今天特地传了份源码。降一分。 这是服务端的src,客户端的也有。自己再我的资源中找吧。 -
java 多线程的三种写法
2020-11-05 15:45:15▌多线程是什么 运行一个程序叫做一个线程,运行过程中时处理多个任务,这些任务叫做线程(比如同时播放画面和声音) 注:真正的多线程需要多核CPU同时处理多个线程,单核的是伪多线程(单核同一时间只能处理一个... -
java多线程编程实例
2018-05-25 10:01:221.相关知识:Java多线程程序设计到的知识:(1)对同一个数量进行操作(2)对同一个对象进行操作(3)回调方法使用(4)线程同步,死锁问题(5)线程通信等等2.示例2.1三个售票窗口同时出售20张票程序分析: (1)... -
Java中使用多线程不能明显提高程序效率的一些原因
2021-03-14 22:38:19java中使用多线程不能明显提高程序效率的一些原因.使用多个线程来处理多任务的时候,效率肯定是有提高的.但是必须要慎用,否则容易出现问题.1.多线程主要是为了充分利用多核cpu,大内存这些资源.如果你的硬件跟不上,... -
并发操作之——java多线程常用的锁
2021-09-07 19:29:21并发操作之——java多线程常用的锁 并发操作之——java多线程常用的锁并发操作前言一、共享锁二、互斥锁三、死锁1、偏向锁2、轻量锁3、重量级锁总结 前言 并发操作之——java多线程常用的锁。 一、共享锁 也叫S...