多线程的三组概念
程序和进程
- 程序:在磁盘中存储的一些数据和逻辑的集合,在存储内存中存储。程序不执行,就不会分配内存空间等系统资源,是一个静止的状态。
- 进程:正在执行的程序就是进程,表示程序的一次运行。进程是在运行内存中执行,进程也会有其他系统资源的分配。
进程和线程
- 进程:正在执行的程序。
- 线程:进程中的一条独立的执行路径就是线程。
特点:
(1)进程是由线程组成的
(2)一个进程中至少有一条线程
(3)如果一个进程中有多条线程,那么当前进程就是一个多线程进程
(4)线程没有独立的资源分配,进程共用一个进程中的资源
并行和并发
1、并行:在某一个时刻,多个任务同时在执行。 计算机想要实现并行,就需要多个cpu
2、并发:在某一个时刻,多个任务同时发起,不要求同时执行,在某一个时间段内将多个任务都执行一遍。
多线程的第一种实现方式
- 方式:继承方式
- 步骤:
1、自定义一个线程类型
2、继承Thread类型
3、重写父类中的run方法表示一个具体的任务
4、在方法中创建一个线程类对象
5、启动线程 - 特点:
1、新线程的启动需要借助主线程,启动之后两条线程互不影响
2、一旦启动新线程,当前进程中有多条线程,因为只有一个cpu,所有cpu只能在多条线程之间来回切换,去谁那就执行谁。
3、启动线程需要使用start方法,而不是调用run方法
public class Demo01 {
public static void main(String[] args) {
MyClass m = new MyClass();
m.start();
for(int i = 1;i<=100;i++) {
System.out.println("主线程执行方法"+i);
}
}
}
class MyClass extends Thread{
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
System.out.println(i+"新线程执行");
}
}
}
多线程的第二种实现方式
- 方式:实现方式
- 步骤:
1、定义一个类型,表示一个任务类
2、实现Runnable接口
3、重写接口中的run方法表示需要执行的任务
4、在方法中创建一个任务类对象,表示一个具体任务
5、创建一个Thread类的线程对象,将任务对象提交给线程对象
6、启动线程
public class Demo02 {
public static void main(String[] args) {
MyTask m = new MyTask();
Thread t = new Thread(m);
t.start();
for (int i = 1; i <=100 ; i++) {
System.out.println("主线程执行"+i);
}
}
}
class MyTask implements Runnable{
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
System.out.println(i+"新线程执行");
}
}
}
两种方式的比较
- 代码复杂程度:
继承的方式比较简单
实现的方式比较复杂 - 灵活性:
继承的方式:类中的任务只能由当前对象执行,当前对象也只能执行类中定义的任务
实现的方式:同一个任务可以交给不同的线程对象执行,同一个线程对象可以接收不同的任务。 - 扩展性:
继承的方式不能再继承其他父类
实现的方式可以继续继承其他父类
多线程的第三种创建方式
- 实现Callable接口
- 步骤:
(1)定义一个类实现Callable接口
(2)在类中重写call()方法
(3)创建自定义类的对象
(4)创建Future的实现类FutureTask对象,把自定义类对象作为构造方法的参数传递
(5)创建Thread类的对象,把FutureTask对象作为构造方法的参数传递
(6)启动线程
(7)再调用get方法,就可以获取线程结束之后的结果。 - 注意事项:
(1)实现类重写call,可以返回数据
比前两种多一个返回值
(2)要获取返回的数据,需要在启动线程之后获取
public class Demo03 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyCallable m = new MyCallable();
FutureTask<String> fu = new FutureTask<String>(m);
Thread t = new Thread(fu);
t.start();
String s = fu.get();
System.out.println(s);
}
}
class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
for (int i = 1; i <=99 ; i++) {
System.out.println("我朝她走了"+i+"步");
}
return "她向我走了1步";
}
}
Thread类
获取线程名称
- getName() :
获取当前线程名称,如果没有起名,系统会默认起一个名字 Thread-X - 注意事项:
-可以直接在线程类中直接调用此方法获取当前对象线程名称
-在任务类中不能直接调用此方法,因为这个方法属于线程类的方法
public class Demo04 {
public static void main(String[] args) {
Thread t = new Thread();
System.out.println(t.getName());//Thread-0
XiXi x = new XiXi();
x.start();//Thread-2
}
}
class XiXi extends Thread{
@Override
public void run() {
System.out.println(getName());
}
}
设置线程名称
- setName(String name) :给线程对象设置名称
- Thread(String name) :通过构造给线程命名
- Thread(Runnable target, String name) :在接收一个任务的同时给线程命名
注意事项:
如果创建一个自定义类的对象,通过构造给属性赋值,需要调用父类的构造方法
public class Demo04 {
public static void main(String[] args) {
//使用set方法给线程设置名称
Thread t = new Thread();
t.setName("一号");
System.out.println(t.getName());
//使用有参构造命名
Thread t2 = new Thread("二号");
System.out.println(t2.getName());
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("新任务!");
}
};
//有任务对象时,给线程命名
Thread t3 = new Thread(r,"三号");
System.out.println(t3.getName());
}
}
class XiXi extends Thread{
}
获取当前线程对象
- 概念:
如果有一段代码想要执行,肯定在某个方法内执行。某个方法想要执行,肯定是通过一条线程来执行。每一段代码都有一个对应的线程执行。获取正在执行这段代码的线程对象。 - 方法:
currentThread() :是一个静态方法,返回的是执行这段代码的线程对象
public class Demo05 {
public static void main(String[] args) {
HeiHei h = new HeiHei();
Thread t1 = new Thread(h,"一号");
Thread t2 = new Thread(h,"二号");
t1.start();
t2.start();
}
}
class HeiHei implements Runnable{
@Override
public void run() {
System.out.println("需要做的事");
//哪个线程执行这段代码,就返回哪个线程的对象
Thread t = Thread.currentThread();
System.out.println(t.getName());
}
}
//打印结果:需要做的事
//一号
//需要做的事
//二号
线程休眠
- Thread.sleep(long time):让执行这段代码的线程休息一会
time:表示线程休息的时间,单位ms - 特点:
无论哪个线程执行这个方法,该线程都会主动休息time毫秒 - 注意事项:
-在使用的时候会出现一个异常:线程中断异常
-在普通方法中,可以对这个异常进行声明
-在run方法中,只能捕获,不能声明,因为run方法是重写的父类的,父类中没有声明异常子类在重写的时候也不能有任何异常的声明。
public class Demo07 {
//倒数十秒
public static void main(String[] args) {
KeKe k = new KeKe();
Thread t = new Thread(k);
t.start();
}
}
class KeKe implements Runnable{
@Override
public void run() {
for (int i = 10; i >=1 ; i--) {
System.out.println(i);
try {
//ctrl 1
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
守护线程
- 概念:保护其他线程能够正常运行的线程就是守护线程。
- 方法:
-setDaemon(boolean on) :将当前线程对象设置为指定线程,参数为true为守护线程否则为非守护线程。
-isDaemon():判断当前对象是否是一个守护线程 - 特点:
1、随便定义一个线程都默认为非守护线程
2、如果守护线程要守护的线程都结束了,守护线程也会跟着结束。
public class Demo08 {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
while(true) {
System.out.println("新线程工作");
}
}
};
t.setDaemon(true);
System.out.println(t.isDaemon());
t.start();
System.out.println("主方法结束");
}
}
线程优先级
- 概念:每个线程在被cpu执行的时候都具有相同的优先级,被执行的机会一样大。可以通过某个方法让线程的优先级进行调整,优先级大的线程被执行的机会越大,优先级低的线程被执行的机会越小。
- 修改优先级的方法:
setPriority(int newPriority) :设置线程优先级
优先级的范围:1–10 1最小优先级 10最大优先级
如果不设置优先级, 每个线程都是默认为5优先级 - 静态常量:
static int MAX_PRIORITY
线程可以拥有的最大优先级。 10
static int MIN_PRIORITY
线程可以拥有的最小优先级。 1
static int NORM_PRIORITY
分配给线程的默认优先级。 5
public static void main(String[] args) {
Thread t = new Thread("一号一号") {
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
System.out.println(getName()+" "+i);
}
}
};
Thread t1 = new Thread("二号") {
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
System.out.println(i+" "+getName());
}
}
};
//将一号设为最低优先级,每次被cpu执行的机会低,不代表后执行
t.setPriority(Thread.MIN_PRIORITY);//t.setPriority(1);
//将二号设为最高优先级,每次被cpu执行的机会高,不代表先执行
t1.setPriority(Thread.MAX_PRIORITY);//t1.setPriority(10);
t.start();
t1.start();
}
线程安全问题
- 其中一个线程在操作某一个数据的时候,这个数据可能也在被另外一条线程操作,就造成多个线程同时在操操作同一个数据,碰到这种情况,此数据就有可能出现错误问题。
- 避免方式:
在执行某一段代码的时候,不要去执行能影响当前代码的代码上去。
保证代码的完整性,一致性
同步代码块
- 格式:
synchronized(锁对象){
需要保证完整性的代码。
} - 原理:
cpu执行带着锁的线程时,该线程需要先获取锁对象,获取到锁对象之后cpu才能执行,并且该段代码不执行结束,该线程就不会释放锁对象,代码执行完之后才会释放锁对象。 - 注意事项:
如果两段代码之间需要互不影响,两个代码段的锁对象必须是一个锁对象。
一般使用本类的字节码对象
public class Demo10 {
public static void main(String[] args) {
Print p = new Print();
Thread t = new Thread() {
public void run() {
while(true) {
p.print1();
}
}
};
Thread t2 = new Thread() {
public void run() {
while(true) {
p.print2();
}
}
};
t.start();
t2.start();
}
}
class Print{
public void print1() {
synchronized (Print.class) {
System.out.print("全");
System.out.print("世");
System.out.println("界");
}
}
public void print2() {
synchronized (Print.class) {
System.out.print("好像");
System.out.print("只有");
System.out.print("我");
System.out.println("疲惫");
}
}
}
同步方法
- 概念:如果一个方法中的所有内容都需要保持同步,可以使用同步方法来代替同步代码块以达到简化代码的操作。
- 格式:
修饰符 synchronized 返回值类型 方法名称(){
} - 如果这个方法是一个非静态方法,那么锁对象默认为当前对象。This
- 如果这个方法是一个静态方法,锁对象默认为当前类的字节码对象.class对象
class Print{
public static synchronized void print1() {
System.out.print("全");
System.out.print("世");
System.out.println("界");
}
public static synchronized void print2() {
synchronized (Print.class) {
System.out.print("好像");
System.out.print("只有");
System.out.print("我");
System.out.println("疲惫");
}
}
}