进程:是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以有多个线程
线程:是指进程中的一个执行任务(控制单元),一个进程中可以运行多个线程,多个线程可共享数据。
主线程 main(了解)
在运行一个简单的Java程序的时候,就已经存在了两个线程,一个是主线程,一个是后台线程——维护的垃圾回收。主线程很特殊,在启动JVM的时候自动启动的。
class MyThread extends Thread {
public void run() {
//自定义线程中的for循环打印i,打印顺序是完全随机的。
for (int i = 0; i < 10; i++) {
System.out.println("MyThread ==> " + i);
}
}
}
public class Demo {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
//主线程中的for循环打印i
for (int i = 0; i < 10; i++) {
System.out.println("main ==> " + i);
}
}
}
class MyRunnable implements Runnable {
public void run() {
//线程体,线程启动时,会自动调用本方法,所有这里是我们写代码的主体部分
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
MyRunnable target = new MyRunnable();
Thread t = new Thread(target);
t.start();
}
}
注意:启动一个新线程,不能使用run()方法,只能使用start方法。
继承方式:
Java中类是单继承的,如果继承了Thread了,该类就不能再有其他的直接父类了。
从操作上分析,继承方式更简单,获取线程名字也简单。
从多线程共享同一个资源上分析,继承方式不能多个线程共享同一个资源。
实现方式:
Java中类可以多实现接口,此时该类还可以继承其他类,并且还可以实现其他接口(设计上,更优雅)。
从操作上分析,获取线程名字也比较复杂,得使用Thread.currentThread()来获取当前线程的引用。
从多线程共享同一个资源上分析,实现方式可以多线程共享同一个资源。
class Apple implements Runnable {
private int num = 50;//苹果总数
public void run() {
try {
for (int i = 0; i < 50; i++) {
if (num > 0) {
Thread.sleep(10);
System.out.println(
Thread.currentThread().getName()+ "吃了编号为:" + num-- + "的苹果"
);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
分析运行结果,为什么有错误的结果。
在这里,打印苹果的编号和苹果数量的减少,应该是一个原子操作,也就说是一个不能分割的操作,两个步骤之间不能被其他线程插一脚。
第一步:System.out.println(Thread.currentThread().getName()
"吃了编号为:" + num + "的苹果");
第二步:num --;
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就执行,其他的线程只能在代码块外等着。
当同一个对象
class Apple implements Runnable {
private int num = 50;//苹果总数
public void run() {
try {
for (int i = 0; i < 50; i++) {
synchronized (this) {
if (num > 0) {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()
"吃了编号为:" + num-- + "的苹果");
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
此时的同步锁this表示Apple对象,而程序中Apple对象只有一份,故可以作为同步锁。
当new 对象多个时,锁不能用this
package 多线程.第五题;
public class Student1 extends Thread{
public Student1(String name){
super(name);
}
private static int number = 50;
private static Object c = new Object();
@Override
public void run() {
for(int i = 0; i < 50; i++){
synchronized (c){
if(number > 0){
System.out.println(getName()+"吃苹果:"+number--);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
synchronized public void doWork(){
///TODO
}
此时同步锁是谁——其实就是,调用当前同步方法的对象:
对于非static方法,同步锁就是this。
对于static方法,同步锁就是当前方法所在类的字节码对象。
class Apple implements Runnable {
private int num = 50;//苹果总数
public void run() {
for (int i = 0; i < 50; i++) {
this.eat();
}
}
synchronized private void eat() {
try {
if (num > 0) {
Thread.sleep(10);
System.out.println(
Thread.currentThread().getName() + "吃了编号为:" + num-- + "的苹果"
);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
好处:保证了多线程并发访问时的同步操作,避免线程的安全性问题。(多线程并发修改同一个资源时存在问题)
缺点:使用synchronized的方法/代码块的性能要低一些。
建议:尽量减小synchronized的作用域。
通过源代码会发现,主要就是方法有没有使用synchronized的区别,比如StringBuilder和StringBuffer。
因此得出结论:使用synchronized修饰的方法性能较低,但是安全性较高,反之则反。