线程的基本概念
线程是一个程序内部的顺序控制流。
线程和进程的区别
1每个进程都有独立代码和数据空间(进程上下文),进程间的切换会有较大的开销
2线程可以看成是轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。
3多进程:在操作系统中能同时运行多个任务(程序)
7多线程:在同一应用程序中有多个顺序流同时执行。
线程
Java的线程是通过java.lang.Thread类来实现的
VM启动时会有一个主方法(public static void main(){})所定义的线程
可以通过创建Thread的实例来创建新的线程。
每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。
通过调用Thread类的start()方法来启动一个线程。也可直接继承Thread但最好用接口。
public class JavaTestThread {
public static void main(String[] args) {
Runner1 r = new Runner1();
Thread t =new Thread(r);
t.start();
for(int i=1;i<=100;i++){
System.out.println("我是main方法里面的"+i);
}
}
}
class Runner1 implements Runnable{
Runner1(){
}
@Override
public void run() {
for(int j=1;j<=100;j++){
System.out.println("我是Runner1方法里面的"+j);
}
}
}
因为新开了一个线程的原因
所以输出会是
我是main方法里面的1
我是main方法里面的2
我是main方法里面的3
我是Runner1方法里面的1
我是main方法里面的4
我是Runner1方法里面的2
我是main方法里面的5
我是Runner1方法里面的3
我是main方法里面的6
我是Runner1方法里面的4
我是main方法里面的7
我是Runner1方法里面的5
我是main方法里面的8
我是Runner1方法里面的6
...
他会两边都在打印不会先执行一个方法再执行一个方法。
isAlive判断线程还活着吗
getPriority()获得线程的优先级数值//即使优先级高也要分给别人点时间片。优先级为1-10,一般为5.
setPriority()设置线程的优先级数值
Thread.sleep()将当前线程睡眠指定毫秒数
可以调用Thread的静态方法:
public static void sleep(long millis)throw interruptedexception
使得当前线程休眠(暂时停止执行millis毫秒)
由于是静态方法,sleep可以由类名直接调用
join()调用某线程的该方法,将当前线程与该线程“合并”,即等待该线程结束,在恢复当前线程的运行。
yield()让出CPU,当前线程进入就绪队列等待调度。
wait()让当前线程wait pool
notify/notifyAll()唤醒对象的wait pool的所有等待线程
线程同步
public class JavaTestThread1 implements Runnable{
Timer timer = new Timer();
public static void main(String[] args) {
JavaTestThread1 test =new JavaTestThread1();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
@Override
public void run() {
timer.add(Thread.currentThread().getName());
}
}
class Timer{
private static int num =0;
public void add(String name){
num++;
try {
Thread.sleep(1);
}catch (InterruptedException e){}
System.out.println(name+"你是第"+num+"个使用timer的线程");
}
}
这段代码希望输出
t1 是第一个使用
t2 是第二个使用
但是实际输出会是
t2你是第2个使用timer的线程
t1你是第2个使用timer的线程
因为t1被打断了 此时num已经变成2;
这时候加上
synchronize(this){
}锁定
还可以public synchronized void add执行方法过程锁定了当前对象
t1你是第1个使用timer的线程
t2你是第2个使用timer的线程
对象互斥锁
在Java语言中,引入了对象互斥锁的概念,保证共享数据操作的完整性。每个对象都对应于一个可以称为互斥锁的标记,这个标记保证在任一时刻,只能有一个线程访问该对象。
关键字synchronized来与对象的互斥锁联系。在某个对象synvhronized修饰时,表明该对象在任意时刻只能有一个线程访问
public class TestDeadLock implements Runnable{
public int flag=1;
static Object o1 = new Object(),o2 =new Object();
public void run(){
System.out.println("flag=" +flag);
if (flag==1){
synchronized (o1){
try{
Thread.sleep(500);
}catch (Exception e){
e.printStackTrace();
}
synchronized (o2){
System.out.println("1");
}
}
}
if (flag==0){
synchronized (o2){
try{
Thread.sleep(500);
}catch (Exception e){
e.printStackTrace();
}
synchronized (o1){
System.out.println("0");
}
}
}
}
public static void main(String[] args) {
TestDeadLock td1 =new TestDeadLock();
TestDeadLock td2 =new TestDeadLock();
td1.flag=1;
td2.flag=0;
Thread t1 =new Thread(td1);
Thread t2 =new Thread(td2);
t1.start();
t2.start();
}
}
死锁了
没拿到锁定的线程不是不能调用其他同步方法,而是执行同步方法的时候会等待其他线程释放锁定之后才能继续往下执行
所以要避免这种问题
public class Test implements Runnable{
int b=100;
public synchronized void m1() throws Exception{
b =1000;
Thread.sleep(5000);
System.out.println("b= "+b);
}
public synchronized void m2()throws Exception{
Thread.sleep(2500);
b=2000;
}
public void run(){
try {
m1();
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception{
Test tt = new Test();
Thread t =new Thread(tt);
t.start();
tt.m2();
System.out.println(tt.b);
}
}
1000
b= 1000
先是main方法的m2锁住在进行。
消费者和生产者
public class TestProducer {
public static void main(String[] args) {
SyncStack ss =new SyncStack();
Producer p = new Producer(ss);
Consumer c = new Consumer(ss);
new Thread(p).start();
new Thread(c).start();
}
}
class ManTou{
int id;
ManTou(int id){
this.id=id;
}
@Override
public String toString() {
return "ManTou{" +
"id=" + id +
'}';
}
}
class SyncStack{
int index=0;
ManTou[] arrMT= new ManTou[6];
public synchronized void push(ManTou mt){
while(index == arrMT.length) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
arrMT[index] = mt;
index++;
}
public synchronized ManTou pop(){
while(index ==0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
index--;
return arrMT[index];
}
}
class Producer implements Runnable{
SyncStack ss =null;
Producer(SyncStack ss){
this.ss=ss;
}
@Override
public void run() {
for (int i=0;i<20;i++){
ManTou mt = new ManTou(i);
System.out.println("生产了:"+mt);
ss.push(mt);
try{
Thread.sleep((long) (Math.random()*100));
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable{
SyncStack ss =null;
Consumer(SyncStack ss){
this.ss=ss;
}
@Override
public void run() {
for (int i=0;i<20;i++){
ManTou mt = ss.pop();
System.out.println("消费了:"+mt);
try{
Thread.sleep((long) (Math.random()*100));
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
我们想达到的目的是 生产者生产他的 消费者消费他的 他们两个互不干扰 他们只和数组里面有多少数据有关
为什么一定要用wait 用sleep的话你不能保证醒来的时候就满足你的条件
为什么要用notify notify能在该线程等待期间唤醒其他线程避免所有线程全部wait造成死锁的情况。
下面是一些网上找的wait与notify方法的知识。
1、wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。
2、wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关键字使用,即,一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。
3、 由于 wait()、notify/notifyAll() 在synchronized 代码块执行,说明当前线程一定是获取了锁的。
当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。
只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。
也就是说,notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程让其获得锁
4、wait() 需要被try catch包围,以便发生异常中断也可以使wait等待的线程唤醒。
5、notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。
6、notify 和 notifyAll的区别
notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。
7、在多线程中要测试某个条件的变化,使用if 还是while?
要注意,notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。所以在进行条件判断时候,可以先把 wait 语句忽略不计来进行考虑;显然,要确保程序一定要执行,并且要保证程序直到满足一定的条件再执行,要使用while进行等待,直到满足条件才继续往下执行。
——————
在看马士兵课程的时候对锁有点意识模糊(同样的代码一会儿又是对象锁和方法锁)在课后上网了解了一下对象锁和方法锁
这个博客写的非常好
https://www.cnblogs.com/QQParadise/articles/5059824.html
所以说我们上述的代码
public class Test implements Runnable{
int b=100;
public synchronized void m1() throws Exception{
b =1000;
System.out.println("我执行了b=1000");
Thread.sleep(5000);
System.out.println("我等待了5秒");
System.out.println("b= "+b);
}
public void m2()throws Exception{
Thread.sleep(2500);
System.out.println("我等待了2.5秒");
b=2000;
System.out.println("我执行了b=2000");
}
public void run(){
try {
m1();
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception{
Test tt = new Test();
Thread t =new Thread(tt);
t.start();
tt.m2();
System.out.println(tt.b);
}
}
当我们取消掉m2的锁时候
我执行了b=1000
我等待了2.5秒
我执行了b=2000
2000
我等待了5秒
b= 2000
再加上的时候
public class Test implements Runnable{
int b=100;
public synchronized void m1() throws Exception{
b =1000;
System.out.println("我执行了b=1000");
Thread.sleep(5000);
System.out.println("我等待了5秒");
System.out.println("b= "+b);
}
public synchronized void m2()throws Exception{
Thread.sleep(2500);
System.out.println("我等待了2.5秒");
b=2000;
System.out.println("我执行了b=2000");
}
public void run(){
try {
m1();
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception{
Test tt = new Test();
Thread t =new Thread(tt);
t.start();
tt.m2();
System.out.println(tt.b);
}
}
我等待了2.5秒
我执行了b=2000
2000
我执行了b=1000
我等待了5秒
b= 1000
此时结果居然和最上面不一样了 我就加了几个sout而已 为什么?