- 线程
- 单线程执行路径
- 一.程序,进程,线程,CPU
- 二:线程与进程与CPU的关系
- 三.创建线程的两种方式
- 1.继承Thread
- 2.实现Runnable接口
- 3.两种方式的联系与区别
- 四.Thread的方法
- 1.构造方法
- 2.线程的优先级
- 3.常用方法
- 五.线程状态
- 六.线程的分类
- 用户线程和守护线程
- 七.几个线程案例
- 八.多线程
- 1.多线程的概念
- 2.线程同步
- (1)并发与并行
- (2)多线程可能遇到的问题(安全问题)【重要】
- 解决 两种方式:【重要】
- (1)synchronized线程同步锁(同步监视器)
- (2)Lock(锁)
- 3.线程死锁
- 4.线程通信
- 线程通信加多线程案例
- 八.新增创建线程方式
程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码,静态代码储存在硬盘里,当程序执行时这段静态代码就变成了进程 。
进程((process):就是正在执行的程序,从Windows角度讲,进程是操作系统进行资源分配的最小单位.,一个进程最少包含一个线程(主线程,main线程),可以包含多个线程
线程(thread):进程可进一步细化为线程,是一个进程内部的最小执行单元,是操作系统进行任务调度的最小单元,隶属于进程,线程只能依赖于一个进程
CPU:一个时间节点只能执行一个任务(线程)
二:线程与进程与CPU的关系1.一个进程至少包含一个线程(主线程,main线程),可以包含多个线程,一个线程只能隶属于一个进程,线程不能脱离进程独立运行
2.每一个进程至少包含一个线程(称为主线程);在主线程中开始执行程序,java程序的入口main()方法就是在主线程中被执行的
3.在主线程中可以创建并启动其他的线程
4.一个进程内的所有线程共享该进程的内存资源
5.CPU以线程为单位进行执行(如果是单核CPU那么一个时间节点只能处理一个任务所有内存中的线程是轮番执行的,而你感觉到的是同时执行的是因为CPU的运行速度太快让你感觉到的是同时执行的(如果是多核CPU的话则一个时间节点能执行多个任务))
三.创建线程的两种方式 1.继承Thread在Java中要实现线程,最简单的方式就是扩展Thread类,重写其中的run方法,方法原型如下:
Thread类中的run方本身并不执行任何操作,如果我们重写了run方法,当线程启动时,它将执行run方法。
具体代码
Mythread继承Thread类
public class Mysthread extends Thread{
@Override
public void run() {
for (int i = 0; i <1000 ; i++) {
System.out.println("MyThread:"+i);
}
}
}
程序启动
public class Main {
public static void main(String[] args) {
Mysthread mysthread=new Mysthread();
mysthread.start();
for (int i = 0; i <1000 ; i++) {
System.out.println("Main"+i);
}
}
}
注:这里创建了继承Thread的MyThread类的对象用直接调用的不是重写的run()方法而是start()方法,如果调用的是run()方法的话那么就不是多线程了就是单线程了
2.实现Runnable接口java.lang.Runnable接口中仅仅只有一个抽象方法 public void run()
以通过实现Runnable接口的方式来实现线程,只需要实现其中的run方法即 可
Runnable接口的存在主要是为了解决Java中不允许多继承的问题。(怎么理解还没看懂)
具体代码
实现Runnable接口
public class Mysthread implements Runnable{
@Override
public void run() {
for (int i = 0; i <1000 ; i++) {
System.out.println("MyThread:"+i);
}
}
}
启动程序
public class Main {
public static void main(String[] args) {
Mysthread mysthread=new Mysthread();
Thread t=new Thread(mysthread);
t.start();
for (int i = 0; i <1000 ; i++) {
System.out.println("Main"+i);
}
}
3.两种方式的联系与区别
区别
(1)继承Thread: 线程代码存放Thread子类run方法中。
(2)实现Runnable:线程代码存在接口的子类的run方法。
实现Runnable的好处 (下午问还是不太懂)
(1)避免了单继承的局限性
(2)多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处 理同一份资源
好处具体体现代码
public class Main2 {
public static void main(String[] args) {
Mysthread mysthread = new Mysthread();
Thread t1 = new Thread(mysthread, "线程一"); //两个线程共用一个Runnable对象
Thread t2 = new Thread(mysthread, "线程二");
t1.start();
t2.start();
Thread.currentThread().setPriority(1); //也可以设置主线程的优先级
for (int i = 0; i < 1000; i++) {
System.out.println("Main线程:" + i);
}
}
}
四.Thread的方法
1.构造方法
和两种线程的创建方式结合起来
2.线程的优先级(1)
- 事实上,计算机只有一个CPU,各个线程轮流获得CPU的使用权,才能 执行任务;
- 优先级较高的线程有更多获得CPU的机会,反之亦然;
- 优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级 都是5,但是也可以通过setPriority和getPriority方法来设置或返回优 先级;
(2)调度策略(CPU执行线程)
1)时间片
给线程奉陪单位时间去执行。根据任务的不同操作系统算法的计算分配的时间片也不同
2)抢占式
高优先级的线程,有更多执行权,但是不是说低优先级就没有机会去执行
3)三个优先级
Thread类有如下3个静态常量来表示优先级
MAX_PRIORITY:取值为10,表示最高优先级。
MIN_PRIORITY:取值为1,表示最底优先级。
NORM_PRIORITY:取值为5,表示默认的优先级。
4)设置和查看优先级的方法
final void setpriority( int newpriority))设置线程的优先级
final int getpriority0返回线程的优先级
5)具体代码
public class Main2 {
public static void main(String[] args) {
Mysthread mysthread=new Mysthread();
Thread t1=new Thread(mysthread,"线程一");
Thread t2=new Thread(mysthread,"线程二");
t1.setPriority(10);
t2.setPriority(1);
t1.start();
t2.start();
Thread.currentThread().setPriority(1); //主线程也可以设置优先级
for (int i = 0; i <1000 ; i++) {
System.out.println("Main线程:"+i);
}
System.out.println("======主线程的优先级:"+ Thread.currentThread().getPriority());
System.out.println("======线程一的优先级:"+t1.getPriority());
System.out.println("======线二程的优先级:"+t2.getPriority());
}
}
3.常用方法
方法应用代码
sleep(long millis) 让当前正在执行的线程线程休眠(暂停执行),休眠时间可以指定
public class Mysthread extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
join() 等待线程终止
public class Main {
public static void main(String[] args) throws InterruptedException {
Mysthread mysthread = new Mysthread();
Thread t = new Thread(mysthread);
Thread t1 = new Thread(mysthread);
t.start();
t.join();
t1.start();
for (int i = 0; i < 100; i++) {
System.out.println("Main" + i);
}
}
}
final void setpriority( )设置线程的优先级
final int getpriority()返回线程的优先级
public class Main2 {
public static void main(String[] args) {
Mysthread mysthread=new Mysthread();
Thread t1=new Thread(mysthread,"线程一");
Thread t2=new Thread(mysthread,"线程二");
t1.setPriority(10);
t2.setPriority(1);
t1.start();
t2.start();
Thread.currentThread().setPriority(1); //主线程也可以设置优先级
for (int i = 0; i <1000 ; i++) {
System.out.println("Main线程:"+i);
}
System.out.println("======主线程的优先级:"+ Thread.currentThread().getPriority());
System.out.println("======线程一的优先级:"+t1.getPriority());
System.out.println("======线二程的优先级:"+t2.getPriority());
}
}
五.线程状态
线程在它的生命周期中会处于不同的状态:
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对 象处于新建状态
**就绪:**处于新建状态的线程被start()后,将进入线程队列等待CPU时 间片,此时它已具备了运行的条件,只是没分配到CPU资源
**运行:**当就绪的线程被调度并获得CPU资源时,便进入运行状态,run ()方法定义了线程的操作和功能
**阻塞:**在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
**死亡:**线程完成了它的全部工作或线程被提前强制性地中止或出现异常 导致结束
六.线程的分类 用户线程和守护线程用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作; 只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
守护线程的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没 有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了
注意:设置线程为守护线程必须在启动线程之前,否则会跑出一个 IllegalThreadStateException异常。
具体代码:
用户线程
public class ThreadDemo1 extends Thread{
Socket socket;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
守护线程
public class ThreadDemo2 extends Thread{
@Override
public void run() {
while(true){
System.out.println("我是守护线程,我在默默到的守护者你");
}
}
}
运行程序
public class Test {
public static void main(String[] args) {
ThreadDemo1 userThread = new ThreadDemo1();
userThread.start();
ThreadDemo2 sh = new ThreadDemo2();
sh.setDaemon(true);//设置线程2为守护线程,必须在启动前设置
sh.start();
}
}
七.几个线程案例
1.实现两人互动聊天
服务器端
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9966);
Socket socket = serverSocket.accept(); //监听
ReceptionThread.socket = socket;
SendThread.socket = socket;
System.out.println("开始聊天");
ReceptionThread receptionThread=new ReceptionThread();
receptionThread.setName("服务:"); //设置名称
SendThread sendThread=new SendThread();
sendThread.setName("服务:"); //设置名称
receptionThread.start();
sendThread.start();
}
}
写出数据(Write)线程
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Date;
import java.util.Scanner;
public class SendThread extends Thread{
static Socket socket;
@Override
public void run() {
Scanner scanner=new Scanner(System.in);
System.out.println("开始聊天");
while (true){
try {
String s=scanner.nextLine();
OutputStream out=socket.getOutputStream();
byte[] bytes=new byte[100];
Date date=new Date();
bytes=(Thread.currentThread().getName()+s+date.toLocaleString()).getBytes();
int lenght=bytes.length;
out.write(bytes,0,lenght);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
读取数据(read)线程
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class ReceptionThread extends Thread {
static Socket socket;
@Override
public void run() {
while (true){
try {
InputStream in=socket.getInputStream();
byte[] bytes=new byte[1024];
int lenght=in.read(bytes);
String s=new String(bytes,0,lenght);
System.err.println(s);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端
import java.io.IOException;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 9966);
ReceptionThread.socket = socket;
SendThread.socket = socket;
System.out.println("开始聊天");
ReceptionThread receptionThread=new ReceptionThread();
receptionThread.setName("客户:");
SendThread sendThread=new SendThread();
sendThread.setName("客户:");
receptionThread.start();
sendThread.start();
}
}
结果
多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多 个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
何时需要多线程
程序需要同时执行两个或多个任务。
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、 网络操作、搜索等。
需要一些后台运行的程序时
多线程的优缺点
(1)优点
- 程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、 网络操作、搜索等。
- 需要一些后台运行的程序时
(2)缺点
- 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
- 多线程需要协调和管理,所以需要CPU时间跟踪线程;
- 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;(多核单核不存在这样的问题)
并发:多个CPU执行多个任务。多个线程同时处理多任务
并行:单个CPU同时(宏观)执行多个任务(在单CPU系统中,每一时刻只能有一道程序执行(时间片),故在微观上这些任务只能是分时的交替进行因为CPU的速度太快所以你感觉它们是同时进行的)
(2)多线程可能遇到的问题(安全问题)【重要】多核CPU:会出现多个线程同时访问一个资源而出现安全问题(比如:线程1进来了但是在执行能做出区分的代码之前线程2也进来了,这就出现问题了,如果两个线程访问同一个资源就会出现问题)
添加线程同步锁(同步监视器)确保一个时间点只有一个线程访问共享资源,给共享资源添加一把锁,哪个线程获取了这把锁,才有权利访问该共享资源
同步监视器
同步监视器可以是任何对象,必须唯一,保证多个线程获得是同一个对象
(锁).
同步监视器的执行过程
1.第一个线程访问,锁定同步监视器,执行其中代码.
2.第二个线程访问,发现同步监视器被锁定,无法访问.
3.第一个线程访问完毕,解锁同步监视器.
4.第二个线程访问,发现同步监视器没有锁,然后锁定并访问.
对应线程的两种创建方式有两种添加方式
Thread方式:
synchronized的第一种添加方式
public class ThreadDemo extends Thread {
static int num = 10;
static Object object = new Object();
@Override
public void run() {
while (true) {
synchronized (object) {
if (num > 0) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "正在卖第" + num + "张票");
num--;
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("票已经卖完");
break;
}
}
}
}
}
synchronized的第二种添加方式
public class ThreadDeom2 extends Thread {
static int num = 10;
@Override
public void run() {
while (true) {
if (num > 0) {
print();
} else {
break;
}
}
}
public static synchronized void print() {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖第" + num + "张票");
}
}
}
启动程序
public class Test {
public static void main(String[] args) {
ThreadDemo threadDemo=new ThreadDemo();
threadDemo.setName("窗口一");
ThreadDemo threadDemo1=new ThreadDemo();
threadDemo1.setName("窗口二");
threadDemo.start();
threadDemo1.start();
}
}
注:第一种synchronized()的添加方式要保证()中传入的是同一个对象,所以在创建的引用变量前加了static修饰保证创建的引用对象只加载一次,保证(不同的线程(都是同一个类的对象))每次在synchronized()中传入的都是同一个对象。第二种synchronized的添加方式用static修饰方法,其实也是要保证是同一个对象只不过在这里是Class对象 为什么要是同一个对象因为是同一个对象多个线程对着同一个对象中的方法代码sychronized才有意义(当有一个线程访问这个个对象中的方法代码时才能将这个对象中的方法或代码锁住不让其他的线程访问如果要是不同对象的话sychronized就没有意义了)
Runnable方式:
synchronized的第一种添加方式
public class ThreadDemo implements Runnable {
int num = 10;
@Override
public void run() {
// 第一种方式
while (true) {
synchronized (this) {
try {
Thread.sleep(1000);
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖第" + num + "张票");
num--;
} else {
System.out.println("票已经卖完");
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
这里的this是当前运行的对象
synchronized的第二种添加方式
public class ThreadDemo implements Runnable {
int num = 10;
@Override
public void run() {
while (true){
if (num>0){
print();
}else{
System.out.println("票已售罄");
break;
}
}
}
public synchronized void print() {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖第" + num + "张票");
num--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注:因为Runnable与Thread创建线程的不同方式所以synchronized的用法也不同,但有一点都是一样的就是synchronized必须对应的是同一个对象不论是普通对象还是Class对象
(2)Lock(锁)Lock
从JDK 5.0开始,Java提供了更强大的线程同步机制-通过显式定义同步锁对象 来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁, 线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存 语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加 锁、释放锁
注:Lock加在代码块两端
一个买票案例具体代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TicketThread implements Runnable{
int num = 10;//共享变量
Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
if(num>0){
print();
}else{
break;
}
}
}
public void print(){
//System.out.println("ssss");
try {
lock.lock(); //加锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
num--;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();//在finally中释放 锁
}
//System.out.println("ssssssssssss");
}
}
public class Test {
public static void main(String[] args) {
//创建出票任务
TicketThread ticketThread = new TicketThread();
//两个线程执行一个任务 num是只有一个的
Thread t1 = new Thread(ticketThread,"窗口1");
Thread t2 = new Thread(ticketThread,"窗口2");
t1.start();
t2.start();
}
}
3.线程死锁
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步 资源,就形成了线程的死锁.
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续.
案例:中国人和美国人吃饭
正常情况:
中国人:两个筷子
美国人:刀和叉
特殊情况:
中国人: 一个筷子 一把刀
美国人:一个筷子 一把叉
设计时考虑清楚锁的顺序,尽量减少嵌套的加锁交互数量。
具体案例代码
可能会出现死锁的线程类
public class Dlock extends Thread {
static Object objA = new Object();
static Object objB = new Object();
boolean flag;
public Dlock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (objA) {
System.out.println("ifobjA");
synchronized (objB) {
System.out.println("ifobjB");
}
}
} else {
synchronized (objB) {
System.out.println("elseobjB");
synchronized (objA) {
System.out.println("elseobjB");
}
}
}
}
}
创建多线程并启动程序
public class Test {
public static void main(String[] args) {
Dlock dlock=new Dlock(true);
Dlock dlock1=new Dlock(false);
dlock.start();
dlock1.start();
}
}
死锁结果
**线程通讯指的是多个线程通过消息传递实现相互牵制,相互调度,即线程间的相 互作用。**
涉及三个方法:
- wait()一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
- notify()一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait, 就唤醒优先级高的那个。
- notifyAll()一旦执行此方法,就会唤醒所有被wait的线程。
说明:
- wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方 法中。
- wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中
注:wait(),notify()和synchronized结合起来使用
线程通信加多线程案例案例:
经典例题:生产者/消费者问题
生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台 处取走产品,生产者一次只能生产固定数量的产品(比如:1), 这时柜台中不能 再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来 取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待.
代码
柜台类
public class Counter {
int num=0;
public synchronized void production(){ //生产功能
if (num==0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num++;
System.out.println("生产者生产");
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consumption(){ //消费功能
if (num>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println("消费者消费");
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
生产者线程:
public class ProtuctionThread extends Thread {
Counter counter;
public ProtuctionThread(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
while (true) {
counter.production();
}
}
}
消费者线程
public class ConsumptionThread extends Thread{
Counter counter;
public ConsumptionThread(Counter counter){
this.counter=counter;
}
@Override
public void run() {
while (true)
counter.consumption();
}
}
测试(启动)程序
public class Test {
public static void main(String[] args) {
Counter counter=new Counter();
ConsumptionThread consumptionThread=new ConsumptionThread(counter);
ProtuctionThread protuctionThread=new ProtuctionThread(counter);
consumptionThread.start();
protuctionThread.start();
}
}
结果
实现Callable接口
实现Callable接口与使用Runnable相比,Callable功能更强大些.
-
相比run()方法,可以有返回值
-
方法可以抛出异常
-
支持泛型的返回值
-
需要借助FutureTask类,获取返回结果接收任务
代码
Callable接口下的实现类
import java.util.concurrent.Callable;
public class CallableThread implements Callable {
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i <10 ; i++) {
sum+=i;
}
return sum;
}
}
创建启动应用程序
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableThread callableThread=new CallableThread();
FutureTask futureTask=new FutureTask(callableThread);
Thread t=new Thread(futureTask);
t.start();
Integer a= (Integer) futureTask.get();
System.out.println(a);
}
}
注:重写的call()方法返回值必须是引用类型



