线程知识总结
进程:就是一个正在运行的程序,分配内存让应用程序能够运行。
Windows系统号称多任务(可以同时运行多个应用程序)。
宏观上看:Windows确实是运行多个程序。
微观上看:CPU快速切换执行任务,由于速度特别快,我们人感觉不到这个切换的过程。
线程:在一个进程中负责代码的执行,就是进程中的执行路径。
疑问:没有主线程,为什么代码可以执行?
java程序在运行的时候,jvm会帮我们创建一个主线程来执行代码。主线程主要负责main方法中的代码执行。
一个java程序中至少有2个线程:
一个主线程主要负责main方法中的代码执行,一个垃圾回收器线程,负责垃圾回收。
多线程:在一个进程中多个线程同时执行不同的任务。
“同时”:单核CPU快速切换多个线程执行任务
速度特别快,人感觉不到切换。
多线程的好处:
1.解决一个进程中同时执行多个任务的问题、
2.提高资源的利用率。
多线程的弊端:
1.增加CPU的负担。线程不是越多越好。
2.降低了一个进程中线程的执行效率。
3.容易引发线程安全问题。
4.出现死锁现象。
java中创建线程有2种方式:
线程的定义方式一:Thread (线程类)
1.需要定义一个类来继承Thread类。
2.重写thread类中run方法,把自定义线程的任务代码写在run方法中。
* 每一个线程都有自己的任务代码,jvm创建的主线程任务代码就是main方法,
* 自定义的线程的任务代码就写在run方法中,自定义的线程就需要执行run方法中的代码。
3.创建Thread的子类,并且调用start方法开启线程。
注意点:一旦线程开启了,会默认执行线程对象中的run方法,但是千万不要自己直接调用run方法,如果直接调用了run方法,就和普通方法没有区别。
Java 是单线程,如果一个类已经继承了别的类,这个时候就不能够通过继承thread类来创建线程了。
线程的使用细节:
1.线程的启动使用父类的start()方法
2.如果线程对象直接调用run(),那么JVm不会当作线程来运行,会认为是普通的方法调用。
3.线程的启动只能由一次,否则抛出异常
4.可以直接创建Thread类的对象并启动该线程,但是如果没有重写run(),什么也不执行。
5.匿名内部类的线程实现方式
线程的定义方式二:
1.自定义一个类实现Runable接口 , 接口中就会提供一个run方法
2.实现Runable接口中的run方法。将线程中的任务写在run方法中
3.创建Runable接口的实现类对象
注意点:实现runnable接口的类不是一个实现类,他是没有能力开启线程的只有是thread或者他的子类才是线程类,只有他们才有开启线程的能力。
4.创建一个Thread对象,并把Runable实现类创建的对象作为参数。
5.调用Thread对象的start方法来开启线程
问题 : 为什么要将Runable接口实现类的对象作为参数传递?
为了让对象中的run方法能够在线程中的run方法中执行。也就是能够将对象中的run方法最为线程中的任务来执行
* 推荐使用 :第二种方式 实现Runable.
* java 是单继承 ,多实现的
售票的线程方式一(thread):
class SaleTickets extends Thread { static int num = 50; // 总票数 共享的数据,三个窗口同时操作同一份数据 //static Object o = new Object(); public SaleTickets (String name) { super(name); } // 重写run方法:卖票的任务 public void run() { while (true) { synchronized ("hh") { //任意类型的对象,锁对象应该是同一个对象 try { Thread.sleep(500); } catch (InterruptedException e) { // TODO: handle exception e.printStackTrace(); } if(num > 0) { System.out.println(this.getName() + "卖了第" + num + "票"); num--; } else { System.out.println("票已售完"); break; } } } } } public class Demo4 { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub SaleTickets t1 = new SaleTickets("窗口1"); SaleTickets t2 = new SaleTickets("窗口2"); SaleTickets t3 = new SaleTickets("窗口3"); t1.start(); t2.start(); t3.start(); } }
售票的线程方式二(Runnable):
class SaleTickets1 implements Runnable { static int num = 50; public void run() { while (true) { synchronized ("djj") { if (num > 0) { System.out.println(Thread.currentThread().getName() + "卖出第" + num + "张票"); num--; } else { System.out.println("票已售尽"); break; } } } } } public class Demo8 { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub //创建实现类对象 SaleTickets1 st = new SaleTickets1(); // 创建三个线程对象 Thread t = new Thread(st, "窗口1"); Thread t1 = new Thread(st, "窗口2"); Thread t2 = new Thread(st, "窗口3"); t.start(); t1.start(); t2.start(); } }
java给线程加锁 :
解决线程安全问题的手段:
通过同步代码的方式:
方式一:同步代码块
锁对象可以是任意一个java中的对象
java中的任意一个对象都会有一个对象的状态 ,就可以通过对象的状态来作为锁的一个标识符。
state = 0 表示锁是关闭 state = 1 表示锁打开。
synchronized (锁对象) {
}
同步代码块的使用注意点 :
1.任意一个对象都可以做锁对象
2.如果你在同步代码块中调用了sleep方法 ,不会释放锁对象
3.只有真正存在线程安全的时候才需要使用同步代码块,否则会降低执行效率
4.多线程操作锁对象必须是唯一的 ,否则无效
方式二:同步函数
写法:用synchronize来修饰方法。
如果同步函数是一个非静态的函数:锁对象就是调用者对象。
如果同步函数是一个静态的函数:锁对象同步函数是所在类的字节码(Class对象)
同步函数和同步代码块的区别:
1.同步代码块,可以随意确定同步代码的范围,同步函数只能同步整个函数的代码。
2.同步代码块的锁对象可以自己任意指定,同步函数的锁对象是固定的。
不知道什么时候安全什么时候不安全。
出现线程安全的问题根本原因:
1.存在两个或两个以上的线程。并且线程之间共享着一个资源。
2.多个语句操作了共享资源
线程死锁:
但是如果使用不当会导致线程死锁问题:
A线程等B线程完成, B线程又在等A线程 结果两个人就一直等下去了 ,这个时候就造成了线程死锁。
线程死锁不一定会出现,有可能会出现。
死锁现象的解决方案 : 没有方案 ,尽量避免发生。
实例:
class DeadLock extends Thread {
public DeadLock(String name){
super(name);
}
@Override
public void run() {
if("张三".equals(Thread.currentThread().getName())){
synchronized ("遥控器") { //锁对象就锁住了
System.out.println("张三拿到了遥控器 ,准备去拿电池");
synchronized ("电池") {//已经被锁住了
System.out.println("张三拿到了遥控器和电池,开着空调,在也 不冷了");
}
}
}else if("老王".equals(Thread.currentThread().getName())){
synchronized ("电池") { //锁也被锁住了 电池对象的状态 变为锁住的状态
System.out.println("老王拿到电池,准备去拿遥控器");
synchronized ("遥控器") { //遥控器也被锁住了
System.out.println("老王拿到了遥控器和电池,开着空调,在也 不冷了");
}
}
}
}
}
public class Demo6 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
DeadLock t1 = new DeadLock("张三");
t1.start();
DeadLock t2 = new DeadLock("老王");
t2.start();
线程的通讯:
线程的通讯:一个线程完成自己的任务,去通知另外一个线程去完成另外一个任务。
wait(); 等待 如果线程执行了wait方法 ,那么该线程就会处于一个等待状态,等待状态的线程必须要通过其他线程来调用
notify()方法来唤醒。
notify();唤醒 随机唤醒线程池中的一个线程。
notifyAll(); 唤醒所有等待的线程。
wait和notify的使用注意点 :
1.wait方法和notify方法是属性Object对象
2.wait方法和notify方法必须在同步线程中执行
3.wait方法和notify方法必须有锁对象来调用
消费者和生产者之间的通讯:
class Product { String name; double price; } class Producter extends Thread{ //生产产品 Product p; public Producter(Product p){ this.p = p; } @Override public void run() { // TODO Auto-generated method stub for(int i = 0;i<50;i++){ synchronized (p) { if(i % 2 == 0){ p.name = "苹果"; p.price = 3.5; System.out.println(p.name+":"+p.price); }else { p.name = "香蕉"; p.price = 2.0; System.out.println(p.name+":"+p.price); } //产品以经生产出来 唤醒消费者来消费 p.notify(); try { p.wait(); // 等待消费者提醒 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } class Custem extends Thread{ Product p; // 消费者消费的产品 public Custem(Product p){ this.p = p; } @Override public void run() { // TODO Auto-generated method stub for (int i = 0;i<50;i++){ synchronized (p) { //通过锁对象来调用 if(p.name != null){ System.out.println("吃"+p.name+":"+p.price); } // 没有产品 告诉生产者需要生产产品 //唤醒生产者 p.notify(); try { p.wait(); //等待产品创建 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } public class Demo9 { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub //创建一个产品对象 Product p = new Product(); //创建一个生产者 Producter t1 = new Producter(p); //创建一个消费者 Custem t2 = new Custem(p); t1.start(); t2.start(); } }