【java基础】- 多线程-part1

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();
	}
}

猜你喜欢

转载自blog.csdn.net/lovexiaotaozi/article/details/81171085