目录
多线程的认识
1、什么是程序
为完成特定任务、用某种语言编写的一组指令的集合。即:指一段静态的代码,静态对象。
2、什么是进程
是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。如:运行中的QQ
3、什么是线程
一个进程内部可以执行多个任务,每个任务即为一个线程。
多线程是指一个进行内多个任务(线程)同时运行
4、线程的组成是什么
任何一个线程都具有一下基本的组成部分:
-
CPU时间片:操作系统(OS) 会为每个线程分配执行时间。
-
运行数据:
堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。
栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。
-
线程的逻辑代码。
5、线程的特点是什么
-
线程抢占式执行:
效率高
可防止单- -线程长时间独占CPU
-
在单核CPU中,宏观上同时执行,微观上顺序执行。
6、什么是多线程
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。多线程技术使程序能够同时完成多项任务。多个任务并不是同时执行,而是分时间段交替执行。由于计算机执行速度快,人们感觉它们是同时执行的。
7、多线程的特点
- 同时执行两个或多个任务;
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验;
- 提高计算机系统CPU的利用率;
线程的实现
实现方法
- 通过Tread类来实现;
- 通过Runnable接口实现多线程:
- 实现Callable接口
1、Tread类实现
- 创建对象
- 用run()方法开启线程的逻辑代码;
- start()启动线程;
public class MyTread extends Thread {
public static void main(String[] args) {
MyTread myTread=new MyTread();
myTread.start();//开启线程
for (int i = 0; i < 50; i++) {
System.out.println("==主线程=="+i);
}
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("这是一个线程"+i);
}
}
}
2、Runnable接口实现
public class MyRunnable implements Runnable {
//创建方式一
@Override
public void run() {
for (int i = 0; i <20; i++) {
System.out.println(Thread.currentThread().getName()+"----------"+i);
}
}
//创建方式二
Runnable runnable=new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-------"+i);
}
}
};
public static void main(String[] args) {
MyRunnable myRunnable=new MyRunnable();
Thread thread=new Thread(myRunnable,"我的线程1");
thread.start();//开启线程
Thread thread1=new Thread(myRunnable.runnable,"我的线程2");
thread1.start();
for (int i=0;i<30;i++){
System.out.println(Thread.currentThread().getName()+"------主线程"+i);
}
}
}
线程的生命周期
一个完整的生命周期中通常要经历如下的五种状态:
- 新建:线程的创建;
- 就绪:执行start()方法;
- 运行:执行run()方法
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态;
- 死亡:
自然死亡(程序执行完或程序发生异常,程序结束)
强制死亡(执行stop()方法,断电,杀掉进程)
表示状态的常用方法:
方法 | 用途 |
---|---|
sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
setPriority(int newPriority) | 更改线程的优先级 |
setName(String name) | 改变线程名称,使之与参数 name 相同 |
setDaemon(boolean on) | 将该线程标记为守护线程或用户线程 |
join() | 等待该线程终止 |
isDaemon() | 测试该线程是否为守护线程 |
yield()
public class YieldThread extends Thread {
public static void main(String[] args) {
YieldThread y1=new YieldThread();
YieldThread y2=new YieldThread();
y1.start();
y2.start();
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-----"+i);
Thread.yield();//主动放弃CPU,当前线程主动放弃时间片,回到就绪状态,竞争下一段时间
}
}
}
sleep(long millis)
public class SleepTread extends Thread{
public static void main(String[] args) {
SleepTread sleepTread=new SleepTread();
sleepTread.setName("我的线程");
sleepTread.start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程"+i);
}
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-----"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
setPriority(int newPriority)
public class PriorityThread extends Thread{
public static void main(String[] args) {
PriorityThread p1=new PriorityThread();
PriorityThread p2=new PriorityThread();
PriorityThread p3=new PriorityThread();
p1.setName("p1");
p2.setName("p2");
p3.setName("p3");
//设置启动级别,数字越大,级别越高,启动越早
p1.setPriority(1);
p2.setPriority(5);
p3.setPriority(10);
//启动
p1.start();
p2.start();
p3.start();
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-----"+i);
}
}
}
join()
public class JoinThread extends Thread{
public static void main(String[] args) {
JoinThread j1=new JoinThread();
try {
j1.join();//加入当前线程,阻塞当前线程,直到当前线程执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
j1.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"---主线程---"+i);
}
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-----"+i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
isDaemon()
public class DeamonThread extends Thread{
public static void main(String[] args) {
DeamonThread thread=new DeamonThread();
thread.setDaemon(true);//设置线程为守护线程
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程------"+i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-----"+i);
}
}
}
线程的安全
1、多线程的安全问题
- 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
- 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
- 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。
2、解决线程安全问题常用方法
同步(上锁):当多个线程同时访问同一个资源时,先让让其他线程等待,再让一个线程执行访问之后,再让其他中一个线程执行访问;
3、线程的同步(上锁)
1、实现方式:
-
同步代码块:
synchronized (临界资源对象) { //对临界资源对象加锁
//代码(原子操作)}
-
同步方法:
synchronized 返回值类型方法名称(形参列表0){ //对当前对象(this) 加锁
//代码(原子操作)}
2、同步规则:
-
只有在调用包含同步代码块的方法。或者同步方法时,才需要对象的锁标记。
-
如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
-
已知JDK中线程安全的类:(以下类中的公开方法,均为synchonized修饰的同步方法。)
StringBuffer
Vector
Hashtable
3、死锁:
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。(如:两个人吃饭各拿一只筷子,互不相让)
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
同步实现方式:
同步代码块:
/**
* 同步代码块实现A执行B不执行,B执行A不执行
*/
public class test1 {
private static int index=0;
public static void main(String[] args) throws Exception{
//创建数组
String[] s=new String[5];
//创建两个线程
Runnable n1=new Runnable() {
@Override
public void run() {
//同步代码块,可保证一个线程访问时,另一个线程等待
synchronized (s){
s[index]="hello";
index++;
}
}
};
Runnable n2=new Runnable() {
@Override
public void run() {
synchronized (s){
s[index]="world";
index++;
}
}
};
//创建两个线程对象
Thread t1=new Thread(n1,"A");
Thread t2=new Thread(n2,"B");
//启动线程
t1.start();
t2.start();
//加入线程
t1.join();
t2.join();
System.out.println(Arrays.toString(s));
}
}
同步方法:
/**
* 通过同步方法,实现买票
*/
public class Test2Ticket implements Runnable {
private int tickets=100;
@Override
public void run() {
while (true){
if (!sale()){
break;
}
}
}
//卖票(同步方法)
public synchronized boolean sale(){//锁指的是this,当为静态方法时,锁为类.class
if (tickets<=0){
return false;
}
System.out.println(Thread.currentThread().getName()+"卖了票的"+tickets+"张数");
tickets--;
return true;
}
public static void main(String[] args) {
Test2Ticket domeTicket=new Test2Ticket();
Thread win1=new Thread(domeTicket,"窗口1");
Thread win2=new Thread(domeTicket,"窗口2");
Thread win3=new Thread(domeTicket,"窗口3");
Thread win4=new Thread(domeTicket,"窗口4");
win1.start();
win2.start();
win3.start();
win4.start();
}
}
死锁:
男孩女孩各有一只筷子,吃饭问题
创建对象:
/**
* 两个筷子吃饭问题
*/
public class Mylock {
//创建两个锁
public static Object a=new Object();
public static Object b=new Object();
}
男孩
public class Boy extends Thread {
@Override
public void run() {
synchronized (Mylock.a){//上锁
System.out.println("男带拿到了a");
synchronized (Mylock.b){
System.out.println("男孩拿到了b");
System.out.println("男孩可以吃饭了");
}
}
}
}
女孩
public class Girl extends Thread {
@Override
public void run() {
synchronized (Mylock.b){
System.out.println("女孩有b");
synchronized (Mylock.a){
System.out.println("女孩拿到了a");
System.out.println("女孩可以吃饭了");
}
}
}
}
测试
public class TestDeadLock {
public static void main(String[] args) {
Boy boy=new Boy();
Girl girl=new Girl();
boy.start();
girl.start();
}
}
解决方法:在测试时让一个线程多休眠sleep()一会
public class TestDeadLock {
public static void main(String[] args) {
Boy boy=new Boy();
Girl girl=new Girl();
boy.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
girl.start();
}
}
4、线程的通信
1、简介:
在多线程运行时,每个线程之间了通过同步解各自的状态
2、线程通信时常用方法
-
等待:
wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问 -
通知:
notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
3、可解决的问题:生产者,消费者
若千个生产者在生产产品,这些产品将提供给若千个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。
案例:面包生产与消费问题
面包类:
public class Bread {
private int id;
private String name;
public Bread(int id, String name) {
this.id = id;
this.name = name;
}
public Bread() {
}
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;
}
@Override
public String toString() {
return "Bread{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
存放面包容器
public class BreadCon {
private Bread[] cons=new Bread[6];
//存放面包位置
private int index=0;
//存放面包
public synchronized void input(Bread b){
//判断容器有没有满
if (index>=6){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
cons[index]=b;
System.out.println(Thread.currentThread().getName()+"生产了"+b.getId());
index++;
//唤醒消费者
this.notify();
}
//取出面包
public synchronized void output(){
//判断容器有没有完
if (index<=0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
Bread b=cons[index];
System.out.println(Thread.currentThread().getName()+"消费了"+b.getId());
cons[index]=null;
//唤醒生产者
this.notify();
}
}
生产者
public class Product implements Runnable {
private BreadCon con;
public Product(BreadCon con) {
super();
this.con = con;
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
con.input(new Bread(i,Thread.currentThread().getName()));
}
}
}
消费者
public class Consume implements Runnable {
private BreadCon con;
public Consume(BreadCon con) {
this.con = con;
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
con.output();
}
}
}
测试
public class TextBread {
public static void main(String[] args) {
//创建容器
BreadCon con=new BreadCon();
//生产和消费
Product product=new Product(con);
Consume consume=new Consume(con);
//创建线程对象
Thread t1=new Thread(product,"小明");
Thread t2=new Thread(consume,"小hua");
//启动线程
t1.start();
t2.start();
}
}