线程和进程
进程
含义:一个运行中的应用程序。每个进程都会分配独立的内存空间。
线程
含义:一个进程的执行分支
它们之间的联系
- 一个进程中至少有两个线程在运行。一个执行main的线程,一个垃圾回收线程。
- 线程是进程中的最小执行单位。
- 同一个进程的线程共享方法区和堆内存;栈内存不共享。
特点:
- 使用了多线程之后,main结束之后,其他线程还有可能在运行。
- 单核和8核的cup实现多线程的方法:8核同一时间至少可以有8个线程同时进行;单核的实际上同一时间也只能有一个线程在执行,但是cpu运行非常快,多个线程不断切换,给人宏观上多个线程同时执行的的感觉。
生命周期:
- 要到达运行状态只能通过就绪状态;
- 启动线程后是进入就绪状态
创建线程的两种方法:
继承Thread类
public class _01BuyTicket {
public static void main(String[] args) {
Thread one = new BuyThreader();
Thread two = new BuyThreader();
one.start();
two.start();
}
}
class BuyThreader extends Thread{
private int ticket = 10;
//指定任务
@Override
public void run() {
while(ticket>0){
System.out.println("当前购买的票是:"+ticket);
ticket = ticket - 1;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
实现Runnable接口
public static void main(String[] args) {
Thread one = new BuyThreader();
Thread two = new BuyThreader();
one.start();
two.start();
}
}
class BuyThreader extends Thread{
private int ticket = 10;
//指定任务
@Override
public void run() {
while(ticket>0){
System.out.println("当前购买的票是:"+ticket);
ticket = ticket - 1;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
两个创建方法的区别:
- 实现接口的方法可以多个线程共享资源且还可以继承其他类
- 实现接口方法会有线程安全的问题
注意:run和start方法的区别
启动线程是使用start,使用run只是简单的方法调用而已。
线程休眠
方式 | 区别1 | 区别2 | 注意点 |
sleep(long millis) | 是Thread中的静态方法 | 不会释放锁资源 | |
wait(long millis)/ wait() | 是Object中的方法 | 会释放锁资源 | wait()代表无限等待,通过调用notify()\notifyAll()唤醒 |
public class BuyTicketDemo {
public static void main(String[] args) {
Runnable runnable = new Buyer();
Thread one = new Thread(runnable,"one");
Thread two = new Thread(runnable,"two");
one.start();
two.start();
}
}
class Buyer implements Runnable{
private int ticket=10;
@Override
public void run() {
buy();
}
private void buy() {
//思路
//线程买了一些要休息一下,并且让其他线程可以进入
synchronized (this) {
while(ticket >0){
//notify(); //唤醒其中一个(wait)休眠的线程
notifyAll();//唤醒所有(wait)休眠的线程
System.out.println(Thread.currentThread().getName()+"购买的是"+ticket);
ticket--;
try {
//Thread.sleep(1000); //不会释放资源(锁)
//wait(100); //会释放资源(锁)
if(ticket!=0){
wait(); //无限休眠
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程安全(重点)
含义:多个线程操作一份数据不会发生错乱
我们前面说到,线程共享方法区和堆,所以所以可能存在线程安全问题。也就是多线程操作静态变量或者实例变量会出现线程安全。操作局部变量不会(不共享)。
处理方法:
- 同步锁
- 锁
同步:
同步方法:
public class _01BuyTicket {
public static void main(String[] args) {
Runnable buyer = new Buyer();
Thread one = new Thread(buyer);
Thread two = new Thread(buyer);
one.start();
two.start();
}
}
class Buyer implements Runnable{
private int ticket=10;
//指定线程的任务
@Override
public void run() {
buy();
}
private synchronized void buy(){ //监视对象this,static同步方法的监视对象是 类.class
while(ticket>0){
System.out.println("购买的ticket是:"+ticket);
try {
Thread.sleep(100); //让线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
}
}
}
注意:
- 给方法加上synchronized就是给这个方法加上一个监视对象(相当于锁),监视对象用来监视该方法一次只能有线程执行。(但是其他的监视对象的线程可以进来)。
- 普通方法上的监视对象是this(自身对象);static的监视对象是类.class
同步整个方法让其监视对象只能是this,且方法中有些代码是不需要同步的,导致效率较低;所以我们一般同步代码块。
同步代码块
对上面相同的案例进行改造
public class _03BuyTicket {
同步代码块
//synchronized(监视对象){
//}
public static void main(String[] args) {
Runnable buyer = new Killer();
Thread one = new Thread(buyer);
Thread two = new Thread(buyer);
one.start();
two.start();
}
}
class Killer implements Runnable{
private int ticket=10;
//对象
Object o= new Object(); // 实例变量。实例变量obj也是共享的。
//指定线程的任务
@Override
public void run() {
buy();
}
private void buy(){
System.out.println("hello");
synchronized (this){ //this
while(ticket>0){
System.out.println("购买的ticket是:"+ticket);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
}
}
}
}
注意:监视对象的选择
同步代码块可以自由选择监视对象,可以this,也可以是类中实例对象。
重中之重:这个监视对象一定要选好了。这个监视对象一定是你需要排队执行的这些线程对象所共享的。