1.并发和并行.
并行:两个或多个事件在同一个时间点上发生
并发:两个或多个事件在同一个时间段内发生
2.进程和线程.
进程:一个进程独享一块内存单元.
线程:多个线程共享一个进程的资源,线程是轻量级的进程,耗资源少.
多进程:一个操作系统中同时运行多个程序
多线程:一个进程中同时运行多个任务
3.创建进程的两种方式:
Runtime runtime = Runtime.getRuntime();
runtime.exec("calc");
ProcessBuilder pb = new ProcessBuilder("calc");
pb.start();
//演示在java中开启一个进程,打开计算器.
4.创建和启动线程的两种方式:
1>继承Thread类.
2>实现Runable接口.
5.通过继承Thread类来实现
第一步:创建线程类继承Thread类
第二步:覆盖/重写类中的run方法
第三步:在主线程(main方法)中创建新的线程对象,并启动.注意启动只能用start方式而不能用run方式,否则就变成了调用调用对象的run方法,本质上还是个单线程.
DEMO:
//演示通过多线程模拟边打游戏边听歌
class A extends Thread{
public void run() {
for(int i = 0;i<50;i++) {
System.out.println("听歌...."+i);
}
}
}
public class ExtendsThreadDemo {
public static void main(String[] args) {
for(int i = 0; i<50; i++) {
System.out.println("打游戏..."+i);
if(i==10) {
A a = new A();
a.start();
}
}
}
}
6.通过实现Runnable接口创建多线程
第一步:创建一个类并实现Runnable接口
第二步:覆盖/重写 Runnable接口中的run方法
第三步:在主线程中创建该类的对象
第四步:在主线程中创建Thread对象并将第三步创建的对象作为参数传入Thread类的构造器中
第五步:启动Thread的实例:t.start();
DEMO:
//通过实现Runnable接口来创建多线程,一般不这么做,了解即可
class B implements Runnable{
@Override
public void run() {
for(int i=0;i<50;i++) {
System.out.println("听音乐...."+i);
}
}
}
public class ImplementsRunnableDemo {
public static void main(String[] args) {
for(int i=0;i<50;i++) {
System.out.println("打游戏"+i);
if(i==10) {
Runnable target = new B();
Thread t = new Thread(target);
t.start();
}
}
}
}
7.通过匿名内部类的方式启动多线程(常用,必须掌握)
DEMO:
//使用匿名内部类创建多线程
public class AnonymousInnerClassDemo {
public static void main(String[] args) {
for(int i=0;i<50;i++) {
System.out.println("打游戏..."+i);
if(i==10) {
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<50;i++) {
System.out.println("听音乐..."+i);
}
}
}).start();
}
}
}
}
8.继承Thread方式和实现Runable接口方式对比
继承方式:
1>java中类是单继承的,继承了Thread类就不能再有其它直接父类了.
2>从操作上继承方式更为简单
3>从多线程共享资源的角度来看,继承不能实现资源共享(static修饰的除外)
实现方式:
1>java中类可以实现多个接口,实现了Runnable接口后还可以实现其它接口,同时还可以继承其它父类.因此,在设计上实现Runnable的方式更为强大.
2>从操作上实现方式略微复杂
3>从多线程共享资源的角度来看,实现方式能够共享资源.
9.使用多线程来模拟三个人吃苹果的案例,从而引出线程不安全等问题.
现在有小A,小B,小C三个人比赛吃苹果,苹果总数有50个,每个苹果都有从1-50的编号,用多线程演示谁吃了编号为几的苹果.
有两种实现方式分别如下:
第一种:继承方式,必须加static关键字.
//继承方式模拟3个人一起吃50个苹果
class A extends Thread{
public A(String name) {
super.setName(name);
}
private static int num = 50;//要想三个线程共享这50个苹果,就必须加static修饰符,否则每个人都能吃50个苹果...
public void run() {
for(int i=0;i<50;i++) {
if(num>0) {
System.out.println(Thread.currentThread().getName()+"吃了编号为"+num--+"的苹果");
}
}
}
}
public class ExtendsDemo {
public static void main(String[] args) {
A t1 = new A("小A");
t1.start();
A t2 = new A("小B");
t2.start();
A t3 = new A("小C");
t3.start();
}
}
第二种:实现Runable接口方式
//实现Runable方式模拟三个一起吃50个苹果.
class B implements Runnable{
private int num = 50;
@Override
public void run() {
for(int i=0;i<50;i++) {
if(num>0) {
try {
Thread.sleep(10);//为了让线程不安全问题更明显,所以加了休眠.
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"吃了编号为"+num--+"的苹果");
}
}
}
}
public class ImplDemo {
public static void main(String[] args) {
B b = new B();
new Thread(b,"小A").start();
new Thread(b,"小B").start();
new Thread(b,"小C").start();
}
}
增加了线程休眠后,出现下图这种重复的情况:
原本一个苹果只能被一个人吃,现在被2个人都吃了一遍...
10.解决线程不安全的三种方式:同步代码块,同步方法,锁机制.
1>同步代码块:synchronized(被共享的资源){//需要同步执行的代码块},接着上面吃苹果的案例,写个DEMO:
class C implements Runnable {
private int num = 100;
@Override
public void run() {
for (int i = 0; i < 50; i++) {
synchronized (this) {//这里的this指的就是当前被共享的资源C
if (num > 0) {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "吃了编号为" + num-- + "的苹果");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class SynchronizedDemo {
public static void main(String[] args) {
C c = new C();
new Thread(c, "小A").start();
new Thread(c, "小B").start();
new Thread(c, "小C").start();
}
}
2>同步方法:使用synchronized修饰的方法.保证A线程执行该方法的时候,其它线程只能在方法外等着.
synchronized public void dowork(){//TODO}
同步锁是谁:
对于非static方法,同步锁就是this
对于static方法,我们使用当前方法所在类的字节码对象(A.class).
*不要使用synchronized修饰run方法,修饰之后,某个线程就执行完了所有功能,好比是多个线程出现串行.解决方案是把需要同步执行的业务放在新定义的方法里,在方法上加上synchronized修饰符,然后在run中调用该方法. DEMO:
//同步方法 演示三人吃苹果
class D implements Runnable {
private int num = 60;
@Override
public void run() {
for (int i = 0; i < 50; i++) {
eat();
}
}
private synchronized void eat() {
if (num > 0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "吃了编号为" + num-- + "的苹果");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class SynchronizedMethodDemo {
public static void main(String[] args) {
D d = new D();
new Thread(d, "小A").start();
new Thread(d, "小B").start();
new Thread(d, "小C").start();
}
}
11.synchronized的好与坏
好:保证了多线程并发访问时的同步操作,避免了线程的安全性问题.
坏:使用synchronized的方法/代码块的性能比不用要低一些,因此要尽量减小synchronized的作用域.
*顺势引一道面试题:StringBuffer和StringBuild区别,因为StringBuffer的方法基本都使用了synchronized修饰了,所以StringBuffer相对而言更安全,但性能比StringBuild低一些.
*说说ArrayList和Vector的区别,HashMap和Hashtable的区别. Vector是ArrayList的前身,使用了synchronized修饰,线程安全但性能低,Hashtable也是HashMap的前身.
12.单例设计模式之 饿汉式和懒汉式详解.
饿汉式:
线程安全,以后用的较多的方式.
//饿汉式单例
public class HungrySingletonDemo {
private HungrySingletonDemo() {};
private static HungrySingletonDemo instance = new HungrySingletonDemo();
public static HungrySingletonDemo getInstance() {
return instance;
}
}
懒汉式:
public class LazySingletonDemo {
private LazySingletonDemo() {};
private static LazySingletonDemo instance = null;
public static LazySingletonDemo getInstance() {
if(instance == null) {
instance = new LazySingletonDemo();
}
return instance;
}
}
懒汉式当多个线程同时访问时,可能会创建出多个实例,因此需要使用同步方法对其进行安全优化,但这样做会降低其性能,为了减少synchronized关键字对性能的损耗,可以通过双重检查加锁的方式.
双重检查加锁:实现线程安全,又能够保证性能不受很搭影响,使用volatile关键字,被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能够正确处理该变量.Java1.5以后的版本才适用.值得一提的是,即使双重检查加锁机制可以提高线程安全性,但依旧会损耗性能,因此不太建议大量使用volatile.
即便如此,还是会损耗性能,因此建议以后的单例都采用饿汉式写法,简单粗暴又好用.
13.同步锁(Lock):
Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象,jdk1.5之后开始出现.
常用的有ReentrantLock,是Lock的实现类,贴个DEMO:
class D implements Runnable {
private int num = 50;
private final ReentrantLock lock = new ReentrantLock();//初始化锁对象
@Override
public void run() {
for (int i = 0; i < 50; i++) {
eat();
}
}
public void eat() {
try {
//开启锁,保证每次只有一个人能拿到锁
lock.lock();
if (num > 0) {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "吃了编号为" + num-- + "的苹果");
}
}catch(Exception e) {
e.printStackTrace();
}finally {
//用完了记得释放锁,可以联想多个人上同一间厕所的案例.
lock.unlock();
}
}
}
public class LockDemo {
public static void main(String[] args) {
D d = new D();
new Thread(d, "小A").start();
new Thread(d, "小B").start();
new Thread(d, "小C").start();
}
}