【多线程高并发】1、线程的创建+线程常用方法+synchronized底层

 

1、基本概念

进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。

线程: 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

创建线程的几种方式

public class T02_HowToCreateThread {
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello MyThread!");
        }
    }

    static class MyRun implements Runnable {
        @Override
        public void run() {
            System.out.println("Hello MyRun!");
        }
    }

    static class MyCall implements Callable<String> {

        @Override
        public String call() throws Exception {
            System.out.println("Hello MyCall");
            return "success";
        }
    }

    public static void main(String[] args) {
        // 第一种
        new MyThread().start();

        // 第二种
        new Thread(new MyRun()).start();

        // 第三种 Lambda
        new Thread(() -> {
            System.out.println("Hello Lambda!");
        }).start();

        // 第四种
        Thread t = new Thread(new FutureTask<String>(new MyCall()));
        t.start();

        // 第五种:线程池。
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(() -> {
            System.out.println("Hello ThreadPool");
        });
        service.shutdown();
    }

}
//请你告诉我启动线程的三种方式 1:Thread 2: Runnable 3:Executors.newCachedThrad(实际也是用的前两种)

线程常用的方法:

  • join():调用join方法的线程强制执行,其他线程处于阻塞状态,等该线程执行完后,其他线程再执行。有可能被外界中断产生InterruptedException 中断异常。
  • sleep():在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。休眠的线程进入阻塞状态。
  • yield():调用yield方法的线程,会礼让其他线程先运行。(让出CPU大概率其他线程先运行,小概率自己还会运行)
  • Thread.currentThead():获取当前线程对象
  • getPriority():获取当前线程的优先级
  • setPriority():设置当前线程的优先级
    • 注意:线程优先级高,被CPU调度的概率大,但不代表一定会运行,还有小概率运行优先级低的线程。
  • isAlive():判断线程是否处于活动状态 (线程调用start后,即处于活动状态)
  • interrupt():中断线程
  • wait():导致线程等待,进入堵塞状态。该方法要在同步方法或者同步代码块中才使用的
  • notify():唤醒当前线程,进入运行状态。该方法要在同步方法或者同步代码块中才使用的
  • notifyAll():唤醒所有等待的线程。该方法要在同步方法或者同步代码块中才使用的

常见的线程状态:

  • New(新建状态): 当用new操作符创建一个新线程时,如 new Thread(r), 该线程还没有开始运行还没有调用.start()。这意味着它的状态是new。
  • Runnable(可运行):  一旦调用start()方法,他就会被线程调度器来执行,也就是操作系统来执行,线程就处于runnable状态。
    • Ready就绪状态:就绪状态是说扔到CPU的等待队列里面去排队等待CPU运行
    • Running运行状态:等真正扔到CPU上运行的时候才叫Running运行状态(调用yiled时候会从Running状态跑到Ready状态去,线程调度器选中执行的时候又从Ready状态跑到Running状态去)
  • 结束状态:如果你线程顺利的执行完了就会进去(注意:Teminated完了之后是不可以回到new状态在调用start,完了就是结束了)
  • TimedWaiting等待:a按照时间等待,等时间结束自己就回去了,Thread.sleep(time),o.wait(time),t.join(time),LockSupport.parkNanos(),LockSupport.parkUntil()这些都是关于时间等待的方法。
  • ,LockSUWaiting等待:在运行的时候如果调用了o.wait()、t.join()、LockSupport.park()进入Waiting状态,调用notify()、notifyAll()、LockSupport.unpark()又回到Running状态
  • Blocked阻塞:在同步代码块的情况下没有得到锁就会阻塞状态,获取锁的时候就是就绪状态。

上面的这些状态全是由JVM管理的,因为JVM管理的时候也要通过操作系统,所以呢,那个是操作系统和哪个是JVM时分不开的,JVM是跑在操作系统上的一个普通程序。

 synchronized

  • 保证原子性也保证可见性
  • 可重入性

对某个对象加锁:

public class T {
	
	private int count = 10;
	private Object o = new Object();
	
	public void m() {
		synchronized(o) { //任何线程要执行下面的代码,必须先拿到o的锁
			count--;
			System.out.println(Thread.currentThread().getName() + " count = " + count);
		}
	}
	
}

  synchronized(this):鉴于上述每次都要重新new一个对象,所以用锁定当前对象就行。

public class T {
	
	private int count = 10;
	
	public void m() {
		synchronized(this) { //任何线程要执行下面的代码,必须先拿到this的锁
			count--;
			System.out.println(Thread.currentThread().getName() + " count = " + count);
		}
	}

	public static void main(String[] args) {
		T t = new T();
		t.m();
	}
}

或者可以写成:(和上述等同)

public class T {

	private int count = 10;
	
	public synchronized void m() { //等同于在方法的代码执行时要synchronized(this)
		count--;
		System.out.println(Thread.currentThread().getName() + " count = " + count);
	}
}

静态方法时:是没有this对象的,你不需要new出一个对象来就能执行这个方法,但如果这个上面加一个synchronized的话就代表synchronized(T.class)这里这个synchronized(T.class)锁的就是T类的对象

public class T {

	private static int count = 10;
	
	public synchronized static void m() { //这里等同于synchronized(FineCoarseLock.class)
		count--;
		System.out.println(Thread.currentThread().getName() + " count = " + count);
	}
	
	public static void mm() {
		synchronized(T.class) { //考虑一下这里写synchronized(this)是否可以?
			count --;
		}
	}

	public static void main(String[] args) {
		m();
		mm();
	}
}

补充:同一个ClassLoader空间的class load到内存它是单例的,不同类加载器就不是的,不同类加载器互相之间也不能访问,所以能访问,就是单例。

 

synchronized既能保证原子性,又保证了可见性(前提是通过了同一把锁,singleton的代码不满足这个条件)

public class T implements Runnable {

	private /*volatile*/ int count = 10;
	
	public synchronized void run() { 
		count--;
		System.out.println(Thread.currentThread().getName() + " count = " + count);
	}
	
	public static void main(String[] args) {
		
		for(int i=0; i<5; i++) {
			T t = new T();
			new Thread(t, "THREAD" + i).start();
		}
	}
	
}

 

同步代码和非同步代码是否可以同时调用:

public class T {

    public synchronized void m1() {
        System.out.println(Thread.currentThread().getName() + " m1 start...");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " m1 end");
    }

    public void m2() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " m2 ");
    }

    public static void main(String[] args) {
        T t = new T();
		
		/*new Thread(()->t.m1(), "t1").start();
		new Thread(()->t.m2(), "t2").start();*/

        new Thread(t::m1, "t1").start();
        new Thread(t::m2, "t2").start();
		
		/*
		//1.8之前的写法
		new Thread(new Runnable() {

			@Override
			public void run() {
				t.m1();
			}
			
		});
		*/
    }
}
t1 m1 start...
t2 m2 
t1 m1 end

如果业务允许脏读就可以不用对读过程加锁。

public class Account {
	String name;
	double balance;
	
	public synchronized void set(String name, double balance) {
		this.name = name;

		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		
		this.balance = balance;
	}
	
	public /*synchronized*/ double getBalance(String name) {
		return this.balance;
	}
	
	
	public static void main(String[] args) {
		Account a = new Account();
		new Thread(()->a.set("zhangsan", 100.0)).start();
		
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(a.getBalance("zhangsan"));
		
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(a.getBalance("zhangsan"));
	}
}

synchronized可重入

一个同步方法可以调用另一个同步方法,一个线程可以拥有某个对象得锁,再次申请得时候仍然会得到该对象得锁。也就是说synchronized获得锁是可重入的。

public class T {
	synchronized void m1() {
		System.out.println("m1 start");
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		m2();
		System.out.println("m1 end");
	}
	
	synchronized void m2() {
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("m2");
	}

	public static void main(String[] args) {
		new T().m1();
	}
}

父类调用子类的概念,父类synchronized,子类调用super.m的时候必须可重入,否则就会出问题(调用父类是同一把锁)。所谓的重入锁就是你拿到这把锁之后不停枷锁枷锁,加好几道锁,但锁定的还是同一个对象,去一道就减个1.

public class T {
	synchronized void m() {
		System.out.println("m start");
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("m end");
	}
	
	public static void main(String[] args) {
		new TT().m();
	}
	
}

class TT extends T {
	@Override
	synchronized void m() {
		System.out.println("child m start");
		super.m();
		System.out.println("child m end");
	}
}

异常锁:程序在执行过程中如果出现异常,默认情况锁会被释放,所以在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。

比如:在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。

public class T {
	int count = 0;
	synchronized void m() {
		System.out.println(Thread.currentThread().getName() + " start");
		while(true) {
			count ++;
			System.out.println(Thread.currentThread().getName() + " count = " + count);
			try {
				TimeUnit.SECONDS.sleep(1);
				
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			if(count == 5) {
				int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch,然后让循环继续
				System.out.println(i);
			}
		}
	}
	
	public static void main(String[] args) {
		T t = new T();
		Runnable r = new Runnable() {

			@Override
			public void run() {
				t.m();
			}
			
		};
		new Thread(r, "t1").start();
		
		try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		new Thread(r, "t2").start();
	}
	
}

synchronized得底层原理。

  • jdk早期,synchronized得底层实现是重量级得,都会要去找操作系统申请锁得地步,这就会造成synchronized效率非常低
  • 改进后,才有了锁升级得概念(可以参考:没错,我就是厕所所长!(二)),当我们使用synchronized得时候HotSpot得实现实在何样的,上来之后第一个去访问某把锁线程比如sync(Object),来了之后现在这个Object得头上面markword记录这个线程。如果只有第一个线程访问得时候实际上是没有给这个objet枷锁得,在内部实现得时候,只是记录这个线程得ID(偏向锁)。偏向锁如果有线程争用得话,就升级为自旋锁概念就是用一个while得循环在这进行自旋(升级为自旋锁),jdk1.6规定10次就会再次升级为重量级锁,重量级锁就是去操作系统那里去申请资源。这是个锁升级得过程。(锁升级过程是不可逆得
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hello-zhou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值