韩愈说过这样一句话:“业精于勤荒于嬉,行成于思毁于随””。天才就是无止境刻苦勤奋的努力。成绩优与良;才思浓与淡,都是由勤奋注定的。
进程指正在运行的程序,进程拥有一个完整的、私有的基本运行资源集合。通常,每个进程都有自己的内存空间。
进程往往被看作是程序或应用的代名词,然而,用户看到的一个单独的应用程序实际上可能是一组相互 协作的进程集合。
为了便于进程之间的通信,大多数操作系统都支持进程间通信(IPC),如pipes 和sockets。IPC不仅支持同一系统上的通信,也支持不同的系统。IPC通信方式包括管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等方式,其中 Socket和Streams支持不同主机上的两个进程IPC。
线程有时也被称为轻量级的进程。进程和线程都提供了一个执行环境,但创建一个新的线程比创建一个 新的进程需要的资源要少。
线程是在进程中存在的 — 每个进程最少有一个线程。线程共享进程的资源,包括内存和打开的文件。这样提高了效率,但潜在的问题就是线程间的通信。
多线程的执行是Java平台的一个基本特征。每个应用都至少有一个线程 – 或几个,如果算上“系统”线程的话,比如内存管理和信号处理等。但是从程序员的角度来看,启动的只有一个线程,叫主线程。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。
局部基本类型变量:局部变量存储在线程自己的栈中。也就是说,局部变量永远也不会被多个线程共享。所以,基础类型的 局部变量是线程安全的。下面是基础类型的局部变量的一个例子:
public class ThreadTest {
public static voidmain(String[]args){ MyThread share = new MyThread();
for (int i=0;i<10;i++){
new Thread(share,"线程"+i).start(); }
}
}
class MyThread implementsRunnable{
public void run()
{ int a =0;
++a;
System.out.println(Thread.currentThread().getName()+":"+a);
}
}
//打印结果
线程0:1
线程1:1
线程2:1
线程3:1
线程4:1
线程5:1
线程6:1
线程7:1
线程8:1
线程9:1
无论多少个线程对run()方法中的基本类型a执行++a操作,只是更新当前线程栈的值,不会影响其他线程,也就是不共享数据;
对象的局部引用和基础类型的局部变量不太一样,尽管引用本身没有被共享,但引用所指的对象并没有存储在线程的栈内。所有的对象都存在共享堆中。如果在某个方法中创建的对象不会逃逸出(即该对象不会被其它方法获得,也不会被非局部变量引用 到)该方法,那么它就是线程安全的。实际上,哪怕将这个对象作为参数传给其它方法,只要别的线程获取不到这个对象,那它仍是线程安全的。
对象成员存储在堆上。如果两个线程同时更新同一个对象的同一个成员,那这个代码就不是线程安全的。如果两个线程同时调用同一个实例上的同一个方法并且有更新操作,就会有竞态条件问题。
线程的通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种共享内 存和消息传递。Java的并发采用的是共享内存模型。
Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度 来 看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory) 中, 每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。 本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他 的硬件和编译器优化。
从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
乐观锁:不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止。其实现方式有一种比较典型的就是Compare and Swap( CAS )。
从思想上来说,Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守。CAS属乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。
CAS的缺点:
1.CPU开销较大 在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
2.不能保证代码块的原子性 CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
Java 中的同步块用 synchronized 标记。 同步块在 Java 中是同步在某个对象上。 所有同步在一个对象上 的同步块在同时只能被一个线程进入并执行操作。所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。
上述同步块都同步在不同对象上。实际需要那种同步块视具体情况而定。
下面是一个同步的实例方法:
//不同实例调用不会阻塞
public class MethodSync {
public synchronized void test(){
try {
System.out.println(Thread.currentThread().getName() + " test 进入了同步方法");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " test 休眠结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/*
* 每个线程都会重新创建一个新的对象所以不会阻塞
*/
public class MyThread extends Thread {
@Override
public void run() {
MethodSync sync = new MethodSync();
System.out.println(Thread.currentThread().getName() + " test 准备进入");
sync.test();
}
}
public class Test {
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
}
}
//Thread-1 test 准备进入
//Thread-0 test 准备进入
//Thread-1 test 进入了同步方法
//Thread-0 test 进入了同步方法
//Thread-0 test 休眠结束
//Thread-1 test 休眠结束
//同一个实例调用会阻塞
public class MethodSync {
public synchronized void test1(){
try {
System.out.println(Thread.currentThread().getName() + " test1 进入了同步方法");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " test1 休眠结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 每个线程用同一个MethodSync对象调用test1()所以线程阻塞
*/
public class MyThread extends Thread {
static MethodSync sync = new MethodSync();
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " test 准备进入");
sync.test1();
}
}
public class Test {
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
}
}
//Thread-0 test1 准备进入
//Thread-0 test1 进入了同步方法
//Thread-1 test1 准备进入
//Thread-0 test1 休眠结束
//Thread-1 test1 进入了同步方法
//Thread-1 test1 休眠结束
synchronized关键字锁住了调用当前方法的当前实例,如果不同实例不受同步锁synchronized关键字影响,如果相同实例调用的当前方法则受关键字synchronized约束。
// 敲重点 同一个属性对象才会实现同步
// Integer 负128-正127区间的数是放在缓存里的内存地址一致是同一个属性对象
// 如果不在这里区间的则会创建不同的对象不受同步锁控制
public class MethodSync {
public Integer lockObject;
public MethodSync(Integer lockObject) {
this.lockObject = lockObject;
}
//锁住了实例中的成员变量
public void test2() {
synchronized (lockObject) {
try {
System.out.println(Thread.currentThread().getName() + " test2 进入了同步方法");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " test2 休眠结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " test 准备进入");
MethodSync sync = new MethodSync(127);
sync.test2();
}
}
public class Test {
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
}
}
//Thread-0 test2 准备进入
//Thread-1 test2 准备进入
//Thread-0 test2 进入了同步方法
//Thread-0 test2 休眠结束
//Thread-1 test2 进入了同步方法
//Thread-1 test2 休眠结束
同一个实例对象的成员属性肯定是同一个,此处列举的是不同实例的情况,但是 依旧实现了同步,原因如下:
Integer存在静态缓存,范围是-128 ~ 127,当使用Integer A = 127 或者 Integer A = Integer.valueOf(127) 这样的形式,都是从此缓存拿。如果使用 Integer A = new Integer(127),每次都是一个新的对象。此例中,两个对象实例的成员变量 lockObject 其实是同一个对象,因此实现了同步。还有字符串常量池也要注意。所以此处关注是,同步代码块传参的对象是否是同一个。这跟第二个方式其实是同一种。
//类对象锁,全局锁
public class MethodSync {
//全局锁,类是全局唯一的
public void test3() {
synchronized (MethodSync.class) {
try {
System.out.println(Thread.currentThread().getName() + " test3 进入了同步方法");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " test3 休眠结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " test 准备进入");
MethodSync sync = new MethodSync();
sync.test3();
}
}
public class Test {
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
}
}
//Thread-0 test3 准备进入
//Thread-1 test3 准备进入
//Thread-0 test3 进入了同步方法
//Thread-0 test3 休眠结束
//Thread-1 test3 进入了同步方法
//Thread-1 test3 休眠结束
JLS规范里面有明确的定义static方法锁的是 Class object synchronized 修饰静态方法锁的是类对象,全局锁。
public class MethodSync {
//全局锁,类是全局唯一的
public static synchronized void test4() {
synchronized (MethodSync.class) {
try {
System.out.println(Thread.currentThread().getName() + " test4 进入了同步方法");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " test4 休眠结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " test 准备进入");
MethodSync.test4();
}
}
public class Test {
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
}
}
//Thread-0 test4 准备进入
//Thread-1 test4 准备进入
//Thread-0 test4 进入了同步方法
//Thread-0 test4 休眠结束
//Thread-1 test4 进入了同步方法
//Thread-1 test4 休眠结束
静态方法的synchronized,锁住了该方法所在的类对象上,因为一个类只能对应一个类对象,所以同时只有一个线程执行类中的静态同步方法.
领取专属 10元无门槛券
私享最新 技术干货